1
0
Fork 0
mirror of https://github.com/iNavFlight/inav-configurator.git synced 2025-07-12 19:10:21 +03:00
inav-configurator/js/serial_queue.js
2024-04-26 22:20:41 +02:00

319 lines
No EOL
9.1 KiB
JavaScript

'use strict';
const CONFIGURATOR = require('./data_storage');
const MSPCodes = require('./msp/MSPCodes');
const SimpleSmoothFilter = require('./simple_smooth_filter');
const eventFrequencyAnalyzer = require('./eventFrequencyAnalyzer');
const mspDeduplicationQueue = require('./msp/mspDeduplicationQueue');
var mspQueue = function () {
var publicScope = {},
privateScope = {};
privateScope.handlerFrequency = 100;
privateScope.balancerFrequency = 20;
privateScope.loadFilter = new SimpleSmoothFilter(1, 0.85);
privateScope.roundtripFilter = new SimpleSmoothFilter(20, 0.95);
privateScope.hardwareRoundtripFilter = new SimpleSmoothFilter(10, 0.95);
/**
* Target load for MSP queue. When load is above target, throttling might start to appear
* @type {number}
*/
privateScope.targetLoad = 2;
privateScope.statusDropFactor = 0.75;
privateScope.currentLoad = 0;
privateScope.removeCallback = null;
privateScope.putCallback = null;
privateScope.queue = [];
privateScope.softLock = false;
privateScope.hardLock = false;
privateScope.lockMethod = 'soft';
privateScope.queueLocked = false;
publicScope.setremoveCallback = function(cb) {
privateScope.removeCallback = cb;
}
publicScope.setPutCallback = function(cb) {
privateScope.putCallback = cb;
}
/**
* Method locks queue
* All future put requests will be rejected
*/
publicScope.lock = function () {
privateScope.queueLocked = true;
};
/**
* Method unlocks queue making it possible to put new requests in it
*/
publicScope.unlock = function () {
privateScope.queueLocked = false;
};
publicScope.setLockMethod = function (method) {
privateScope.lockMethod = method;
};
publicScope.getLockMethod = function () {
return privateScope.lockMethod;
};
publicScope.setSoftLock = function () {
privateScope.softLock = new Date().getTime();
};
publicScope.setHardLock = function () {
privateScope.hardLock = new Date().getTime();
};
publicScope.freeSoftLock = function () {
privateScope.softLock = false;
};
publicScope.freeHardLock = function () {
privateScope.hardLock = false;
};
publicScope.isLocked = function () {
if (privateScope.lockMethod === 'soft') {
return privateScope.softLock !== false;
} else {
return privateScope.hardLock !== false;
}
};
privateScope.getTimeout = function (code) {
if (code == MSPCodes.MSP_SET_REBOOT || code == MSPCodes.MSP_EEPROM_WRITE) {
return 5000;
} else {
return CONFIGURATOR.connection.getTimeout();
}
};
/**
* This method is periodically executed and moves MSP request
* from a queue to serial port. This allows to throttle requests,
* adjust rate of new frames being sent and prohibit situation in which
* serial port is saturated, virtually overloaded, with outgoing data
*
* This also implements serial port sharing problem: only 1 frame can be transmitted
* at once
*
* MSP class no longer implements blocking, it is queue responsibility
*/
publicScope.executor = function () {
/*
* Debug
*/
eventFrequencyAnalyzer.put("execute");
privateScope.loadFilter.apply(privateScope.queue.length);
/*
* if port is blocked or there is no connection, do not process the queue
*/
if (publicScope.isLocked() || CONFIGURATOR.connection === false) {
eventFrequencyAnalyzer.put("port in use");
return false;
}
var request = privateScope.get();
if (request !== undefined) {
/*
* Lock serial port as being in use right now
*/
publicScope.setSoftLock();
publicScope.setHardLock();
request.timer = setTimeout(function () {
console.log('MSP data request timed-out: ' + request.code);
mspDeduplicationQueue.remove(request.code);
/*
* Remove current callback
*/
privateScope.removeCallback(request.code);
/*
* To prevent infinite retry situation, allow retry only while counter is positive
*/
if (request.retryCounter > 0) {
request.retryCounter--;
/*
* Create new entry in the queue
*/
publicScope.put(request);
}
}, privateScope.getTimeout(request.code));
if (request.sentOn === null) {
request.sentOn = new Date().getTime();
}
/*
* Set receive callback here
*/
privateScope.putCallback(request);
eventFrequencyAnalyzer.put('message sent');
/*
* Send data to serial port
*/
CONFIGURATOR.connection.send(request.messageBody, function (sendInfo) {
if (sendInfo.bytesSent == request.messageBody.byteLength) {
/*
* message has been sent, check callbacks and free resource
*/
if (request.onSend) {
request.onSend();
}
publicScope.freeSoftLock();
}
});
}
};
privateScope.get = function () {
return privateScope.queue.shift();
};
publicScope.flush = function () {
privateScope.queue = [];
};
/**
* Method puts new request into queue
* @param {MspMessageClass} mspRequest
* @returns {boolean} true on success, false when queue is locked
*/
publicScope.put = function (mspRequest) {
const isMessageInQueue = mspDeduplicationQueue.check(mspRequest.code);
if (isMessageInQueue) {
eventFrequencyAnalyzer.put('MSP Duplicate ' + mspRequest.code);
return false;
}
if (privateScope.queueLocked === true) {
return false;
}
mspDeduplicationQueue.put(mspRequest.code);
privateScope.queue.push(mspRequest);
return true;
};
publicScope.getLength = function () {
return privateScope.queue.length;
};
/**
* 1s MSP load computed as number of messages in a queue in given period
* @returns {number}
*/
publicScope.getLoad = function () {
return privateScope.loadFilter.get();
};
publicScope.getRoundtrip = function () {
return privateScope.roundtripFilter.get();
};
/**
*
* @param {number} number
*/
publicScope.putRoundtrip = function (number) {
privateScope.roundtripFilter.apply(number);
};
publicScope.getHardwareRoundtrip = function () {
return privateScope.hardwareRoundtripFilter.get();
};
/**
*
* @param {number} number
*/
publicScope.putHardwareRoundtrip = function (number) {
privateScope.hardwareRoundtripFilter.apply(number);
};
publicScope.balancer = function () {
privateScope.currentLoad = privateScope.loadFilter.get();
/*
* Also, check if port lock if hanging. Free is so
*/
var currentTimestamp = new Date().getTime(),
threshold = publicScope.getHardwareRoundtrip() * 3;
if (threshold > 5000) {
threshold = 5000;
}
if (threshold < 1000) {
threshold = 1000;
}
if (privateScope.softLock !== false && currentTimestamp - privateScope.softLock > threshold) {
publicScope.freeSoftLock();
eventFrequencyAnalyzer.put('force free soft lock');
}
if (privateScope.hardLock !== false && currentTimestamp - privateScope.hardLock > threshold) {
console.log('Force free hard lock');
publicScope.freeHardLock();
eventFrequencyAnalyzer.put('force free hard lock');
}
};
/**
* This method return periodic for polling interval that should populate queue in 80% or less
* @param {number} requestedInterval
* @param {number} messagesInInterval
* @returns {number}
*/
publicScope.getIntervalPrediction = function (requestedInterval, messagesInInterval) {
var requestedRate = (1000 / requestedInterval) * messagesInInterval,
availableRate = (1000 / publicScope.getRoundtrip()) * 0.8;
if (requestedRate < availableRate) {
return requestedInterval;
} else {
return (1000 / availableRate) * messagesInInterval;
}
};
publicScope.getQueue = function () {
return privateScope.queue;
};
setInterval(publicScope.executor, Math.round(1000 / privateScope.handlerFrequency));
setInterval(publicScope.balancer, Math.round(1000 / privateScope.balancerFrequency));
return publicScope;
}();
module.exports = mspQueue;