Compare commits
3 Commits
0a9d4b1dda
...
2540d19b76
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2540d19b76 | ||
| 30908365ba | |||
| 950ca2b6b4 |
@@ -32,6 +32,32 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Flow",
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"name": "VegaFlow 10",
|
||||||
|
"units": ["m³/h", "gpm", "l/min"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VegaFlow 20",
|
||||||
|
"units": ["m³/h", "gpm", "l/min"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Level",
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"name": "VegaLevel 10",
|
||||||
|
"units": ["m", "ft", "mm"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VegaLevel 20",
|
||||||
|
"units": ["m", "ft", "mm"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Level",
|
"name": "Level",
|
||||||
"models": [
|
"models": [
|
||||||
@@ -68,6 +94,36 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Binder Engineering",
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"name": "Valves",
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "Gate",
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"id": "binder-valve-001",
|
||||||
|
"name": "ECDV",
|
||||||
|
"units": ["m³/h", "gpm", "l/min"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jet",
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"id": "binder-valve-002",
|
||||||
|
"name": "JCV",
|
||||||
|
"units": ["m³/h", "gpm", "l/min"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
16
datasets/assetData/curves/ECDV.json
Normal file
16
datasets/assetData/curves/ECDV.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"1.204": {
|
||||||
|
"125": {
|
||||||
|
"x": [0,10,20,30,40,50,60,70,80,90,100],
|
||||||
|
"y": [0,18,50,95,150,216,337,564,882,1398,1870]
|
||||||
|
},
|
||||||
|
"150": {
|
||||||
|
"x": [0,10,20,30,40,50,60,70,80,90,100],
|
||||||
|
"y": [0,25,73,138,217,314,490,818,1281,2029,2715]
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"x": [0,10,20,30,40,50,60,70,80,90,100],
|
||||||
|
"y": [0,155,443,839,1322,1911,2982,4980,7795,12349,16524]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
index.js
4
index.js
@@ -16,7 +16,7 @@ const configUtils = require('./src/helper/configUtils.js');
|
|||||||
// Domain-specific modules
|
// Domain-specific modules
|
||||||
const { MeasurementContainer } = require('./src/measurements/index.js');
|
const { MeasurementContainer } = require('./src/measurements/index.js');
|
||||||
const configManager = require('./src/configs/index.js');
|
const configManager = require('./src/configs/index.js');
|
||||||
const nrmse = require('./src/nrmse/errorMetrics.js');
|
const nrmse = require('./src/nrmse/ErrorMetrics.js');
|
||||||
const state = require('./src/state/state.js');
|
const state = require('./src/state/state.js');
|
||||||
const convert = require('./src/convert/index.js');
|
const convert = require('./src/convert/index.js');
|
||||||
const MenuManager = require('./src/menu/index.js');
|
const MenuManager = require('./src/menu/index.js');
|
||||||
@@ -41,4 +41,4 @@ module.exports = {
|
|||||||
MenuManager,
|
MenuManager,
|
||||||
childRegistrationUtils,
|
childRegistrationUtils,
|
||||||
loadCurve
|
loadCurve
|
||||||
};
|
};
|
||||||
387
src/configs/valve.json
Normal file
387
src/configs/valve.json
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"name": {
|
||||||
|
"default": "valve",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A human-readable name or label for this machine configuration."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"default": null,
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "A unique identifier for this configuration. If not provided, defaults to null."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"default": "m3/h",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The default measurement unit for this configuration (e.g., 'meters', 'seconds', 'unitless')."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"logLevel": {
|
||||||
|
"default": "info",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"value": "debug",
|
||||||
|
"description": "Log messages are printed for debugging purposes."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "info",
|
||||||
|
"description": "Informational messages are printed."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "warn",
|
||||||
|
"description": "Warning messages are printed."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "error",
|
||||||
|
"description": "Error messages are printed."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"default": true,
|
||||||
|
"rules": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Indicates whether logging is active. If true, log messages will be generated."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"functionality": {
|
||||||
|
"softwareType": {
|
||||||
|
"default": "valve",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Specified software type for this configuration."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"default": "controller",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Indicates the role this configuration plays within the system."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"positionVsParent":{
|
||||||
|
"default":"atEquipment",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"value": "atEquipment",
|
||||||
|
"description": "The node is connected at the equipment level and is responsible for controlling or monitoring the equipment as a whole."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "upstream",
|
||||||
|
"description": "The node is connected in a downstream position, indicating it is responsible for monitoring or controlling processes that occur after the equipment's operation, such as product flow or output."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "downstream",
|
||||||
|
"description": "The node is connected in an upstream position, indicating it is responsible for monitoring or controlling processes that occur before the equipment's operation, such as input flow or supply."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Defines the position of the measurement relative to its parent equipment or system."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"asset": {
|
||||||
|
"uuid": {
|
||||||
|
"default": null,
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "A universally unique identifier for this asset. May be null if not assigned."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tagCode":{
|
||||||
|
"default": null,
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Asset tag code which is a unique identifier for this asset. May be null if not assigned."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"geoLocation": {
|
||||||
|
"default": {},
|
||||||
|
"rules": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "An object representing the asset's physical coordinates or location.",
|
||||||
|
"schema": {
|
||||||
|
"x": {
|
||||||
|
"default": 0,
|
||||||
|
"rules": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "X coordinate of the asset's location."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"default": 0,
|
||||||
|
"rules": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Y coordinate of the asset's location."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"z": {
|
||||||
|
"default": 0,
|
||||||
|
"rules": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Z coordinate of the asset's location."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supplier": {
|
||||||
|
"default": "Unknown",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The supplier or manufacturer of the asset."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"default": "valve",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A general classification of the asset tied to the specific software. This is not chosen from the asset dropdown menu."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"default": "gate",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A more specific classification within 'type'. For example, 'centrifugal' for a centrifugal pump."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"default": "Unknown",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A user-defined or manufacturer-defined model identifier for the asset."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"default": "unitless",
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The unit of measurement for this asset (e.g., 'meters', 'seconds', 'unitless')."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accuracy": {
|
||||||
|
"default": null,
|
||||||
|
"rules": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "The accuracy of the machine or sensor, typically as a percentage or absolute value."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"valveCurve": {
|
||||||
|
"default": {
|
||||||
|
"1.204": {
|
||||||
|
"1": {
|
||||||
|
"x": [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
|
||||||
|
"y": [0, 18, 50, 95, 150, 216, 337, 564, 882, 1398, 1870]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"type": "valveCurve",
|
||||||
|
"description": "the first parameter is kg (usually according to 1 normal cubic meter per hour acc. to din norm ) and the second parameter is the diameter in mm. The x values are the opening of the valve in percent and the y values are the KV values in m3/h. The KV value is the flow rate of water at a temperature of 20 degrees Celsius through the valve when it is fully open."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"current": {
|
||||||
|
"default": "auto",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"value": "auto",
|
||||||
|
"description": "Machine accepts setpoints from a parent controller and runs autonomously."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "virtualControl",
|
||||||
|
"description": "Controlled via GUI setpoints; ignores parent commands."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "fysicalControl",
|
||||||
|
"description": "Controlled via physical buttons or switches; ignores external automated commands."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "maintenance",
|
||||||
|
"description": "No active control from auto, virtual, or fysical sources."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The operational mode of the machine."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allowedActions":{
|
||||||
|
"default":{},
|
||||||
|
"rules": {
|
||||||
|
"type": "object",
|
||||||
|
"schema":{
|
||||||
|
"auto": {
|
||||||
|
"default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Actions allowed in auto mode."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"virtualControl": {
|
||||||
|
"default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Actions allowed in virtualControl mode."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fysicalControl": {
|
||||||
|
"default": ["statusCheck", "emergencyStop"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Actions allowed in fysicalControl mode."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maintenance": {
|
||||||
|
"default": ["statusCheck"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Actions allowed in maintenance mode."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Information about valid command sources recognized by the machine."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allowedSources":{
|
||||||
|
"default": {},
|
||||||
|
"rules": {
|
||||||
|
"type": "object",
|
||||||
|
"schema":{
|
||||||
|
"auto": {
|
||||||
|
"default": ["parent", "GUI", "fysical"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Sources allowed in auto mode."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"virtualControl": {
|
||||||
|
"default": ["GUI", "fysical"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Sources allowed in virtualControl mode."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fysicalControl": {
|
||||||
|
"default": ["fysical"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Sources allowed in fysicalControl mode."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Information about valid command sources recognized by the machine."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"default": "parent",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"value": "parent",
|
||||||
|
"description": "Commands are received from a parent controller."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "GUI",
|
||||||
|
"description": "Commands are received from a graphical user interface."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "fysical",
|
||||||
|
"description": "Commands are received from physical buttons or switches."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Information about valid command sources recognized by the machine."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sequences":{
|
||||||
|
"default":{},
|
||||||
|
"rules": {
|
||||||
|
"type": "object",
|
||||||
|
"schema": {
|
||||||
|
"startup": {
|
||||||
|
"default": ["starting","warmingup","operational"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Sequence of states for starting up the machine."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shutdown": {
|
||||||
|
"default": ["stopping","coolingdown","idle"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Sequence of states for shutting down the machine."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"emergencystop": {
|
||||||
|
"default": ["emergencystop","off"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Sequence of states for an emergency stop."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boot": {
|
||||||
|
"default": ["idle","starting","warmingup","operational"],
|
||||||
|
"rules": {
|
||||||
|
"type": "set",
|
||||||
|
"itemType": "string",
|
||||||
|
"description": "Sequence of states for booting up the machine."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Predefined sequences of states for the machine."
|
||||||
|
|
||||||
|
},
|
||||||
|
"calculationMode": {
|
||||||
|
"default": "medium",
|
||||||
|
"rules": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"value": "low",
|
||||||
|
"description": "Calculations run at fixed intervals (time-based)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "medium",
|
||||||
|
"description": "Calculations run when new setpoints arrive or measured changes occur (event-driven)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "high",
|
||||||
|
"description": "Calculations run on all event-driven info, including every movement."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The frequency at which calculations are performed."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
29
src/helper/assertionUtils.js
Normal file
29
src/helper/assertionUtils.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* @file assertionUtils.js
|
||||||
|
*
|
||||||
|
* Utility functions for assertions and throwing errors in EVOLV.
|
||||||
|
*
|
||||||
|
* @description This module provides functions to assert conditions and throw errors when those conditions are not met.
|
||||||
|
* @exports ValidationUtils
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Assertions {
|
||||||
|
/**
|
||||||
|
* Assert that no NaN values are present in an array.
|
||||||
|
* @param {Array} arr - The array to check for NaN values.
|
||||||
|
* @param {string} label - Array label to indicate where the error occurs.
|
||||||
|
*/
|
||||||
|
assertNoNaN(arr, label = "array") {
|
||||||
|
if (Array.isArray(arr)) {
|
||||||
|
for (const el of arr) {
|
||||||
|
assertNoNaN(el, label);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Number.isNaN(arr)) {
|
||||||
|
throw new Error(`NaN detected in ${label}!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Assertions;
|
||||||
@@ -83,6 +83,11 @@ class ChildRegistrationUtils {
|
|||||||
this.connectValve(child);
|
this.connectValve(child);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "machineGroup":
|
||||||
|
this.logger.debug(`Registering complete machineGroup child: ${id}`);
|
||||||
|
this.connectMachineGroup(child);
|
||||||
|
break;
|
||||||
|
|
||||||
case "actuator":
|
case "actuator":
|
||||||
this.logger.debug(`Registering linear actuator child: ${id}`);
|
this.logger.debug(`Registering linear actuator child: ${id}`);
|
||||||
this.connectActuator(child,positionVsParent);
|
this.connectActuator(child,positionVsParent);
|
||||||
@@ -184,6 +189,26 @@ class ChildRegistrationUtils {
|
|||||||
this.logger.info(`Valve ${valveId} registered successfully.`);
|
this.logger.info(`Valve ${valveId} registered successfully.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectMachineGroup(machineGroup) {
|
||||||
|
if (!machineGroup) {
|
||||||
|
this.logger.warn("Invalid machineGroup provided.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const machineGroupId = Object.keys(this.mainClass.machineGroups).length + 1;
|
||||||
|
this.mainClass.machineGroups[machineGroupId] = machineGroup;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Skip machinegroup connnection: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
machineGroup.emitter.on("totalFlowChange", (data) => {
|
||||||
|
this.mainClass.logger.debug('Total flow change of machineGroup detected');
|
||||||
|
this.mainClass.handleInput("parent", "totalFlowChange", data)}); //Geef nieuwe totale flow door aan valveGrouControl
|
||||||
|
|
||||||
|
this.logger.info(`MachineGroup ${machineGroup.config.general.name} registered successfully.`);
|
||||||
|
}
|
||||||
|
|
||||||
connectActuator(actuator, positionVsParent) {
|
connectActuator(actuator, positionVsParent) {
|
||||||
if (!actuator) {
|
if (!actuator) {
|
||||||
this.logger.warn("Invalid actuator provided.");
|
this.logger.warn("Invalid actuator provided.");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class movementManager {
|
|||||||
|
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
this.maxSpeed = maxSpeed;
|
this.maxSpeed = maxSpeed;
|
||||||
|
console.log(`MovementManager: Initial speed=${this.speed}, maxSpeed=${maxSpeed}`);
|
||||||
this.interval = interval;
|
this.interval = interval;
|
||||||
this.timeleft = 0; // timeleft of current movement
|
this.timeleft = 0; // timeleft of current movement
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user