-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathDriverManager.js
237 lines (207 loc) · 8.03 KB
/
DriverManager.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
'use strict';
/** @namespace drivers */
const DriverChecker = require('./DriverChecker');
const TAG = 'DriverManager';
/**
* <p>Class manipulate drivers</p>
*
* DriverManager provides mechanisms to:
* <ul>
* <li>initialize and configure drivers based on config files,</li>
* <li>return instance of driver by driver unique identifier,</li>
* <li>filter drivers by groups,</li>
* <li>check if driver is available,</li>
* <li>resolve dependencies and</li>
* <li>put driver out of order or recover driver if it is possible.</li>
* </ul>
*
* Each driver can be part of one or more of following groups: control, terrain & position.
*
* @memberOf drivers
* @author Darko Lukic <[email protected]>
*/
class DriverManager {
constructor() {
this.drivers = {};
this.driversOutOfOrder = {};
this.config = Mep.Config.get('Drivers');
}
/**
* Initialize all drivers
*/
async init() {
// Iterate through all drivers in config and initialize them
for (let name of Object.keys(this.config)) {
try {
await this._initDriver(name);
} catch (e) {
Mep.Log.error(e);
}
}
}
/**
* Check if driver is out of order
* @param {String} name Unique name of a driver
* @returns {Boolean}
*/
isDriverOutOfOrder(name) {
return (name in this.driversOutOfOrder);
}
/**
* Get driver instance by driver name
*
* @param {String} name Driver name, eg. "MotionDriver", or "ModbusDriver".
* @returns {Object} Required driver
*/
getDriver(name) {
if (this.isDriverAvailable(name) === false) {
throw new Error('There is no driver with name ' + name);
}
return this.drivers[name];
}
/**
* Returns true if driver is available
* @param {String} name Driver name
* @returns {Boolean} Is driver available
*/
isDriverAvailable(name) {
return (name in this.drivers);
}
/**
* <p>Get all drivers that can provide specific type of data.</p>
*
* <p>Every driver that can provide a certain type of data has implemented
* universal mechanism for getting that data from the driver. That is extremely
* useful for services and in that case services implement only logic for
* data processing, not mechanisms for data collection from different drivers.
* Services are in this case also hardware independent.</p>
*
* @param {String} type Data type which driver can provide. Can be: position & terrain.
* @returns {Object} List of filtered drivers
*/
getDriversByGroup(type) {
let filteredDrivers = {};
for (let driverKey in this.drivers) {
if (this.drivers.hasOwnProperty(driverKey) == false) {
continue;
}
// Check if driver has defined list of data misc which can provide
if (typeof this.drivers[driverKey].getGroups !== 'function') {
continue;
}
// Check if driver can provide data
if (this.drivers[driverKey].getGroups().indexOf(type) >= 0) {
filteredDrivers[driverKey] = this.drivers[driverKey];
}
}
return filteredDrivers;
}
/**
* <p>Get all drivers that can provide specific type of data (or execute specific command) and call method.</p>
*
* <p>Eg. imagine you have laptop and monitor. If a monitor is not available then your laptop is it totally ok with it,
* image will be sent only to laptop's monitor. However, if there is a monitor connected to the laptop then laptop
* will be aware of monitor and it will send an image to monitor too. Your laptop don't really recognize difference
* between displays, and it communicates between them in very similar way. To sum up, the same is for this method,
* method will be called to all drivers that are the part of the group.</p>
*
* @param {String} type Data type which driver can provide. Can be: position & terrain.
* @param {String} method Method to be called.
* @param {Array} params Params to be passed to method.
*
* @see DriverManager.getDriversByGroup
*/
callMethodByGroup(type, method, params) {
let drivers = this.getDriversByGroup(type);
for (let key in drivers) {
drivers[key][method](...params);
}
}
/**
* Put driver out of order
* @param {String} name Unique name of a driver
* @param {String} message Describe more why the fault happened
*/
putDriverOutOfOrder(name, message) {
// Check if it is already out of order
if (this.isDriverOutOfOrder(name) === true) {
return;
}
// Move to outOfOther pool
if (this.isDriverAvailable(name) === true) {
delete this.drivers[name];
}
this.driversOutOfOrder[name] = true;
// Notify user
Mep.Log.error(TAG, name, 'is out of the order', message);
}
async _initDriver(driverIdentifier) {
let moduleConfig = this.config[driverIdentifier];
if (moduleConfig === undefined) {
throw Error('Driver ' + driverIdentifier + ' is missing in configuration. ' +
'Some driver probably depends on ' + driverIdentifier + ' driver');
}
if (this.isDriverAvailable(driverIdentifier) || this.isDriverOutOfOrder(driverIdentifier)) {
return;
}
// Load driver
let load = moduleConfig['@load'];
let classPath = moduleConfig['@class'];
// Do not initialize if `load field == false`
if (load === true) {
let ModuleClass;
try {
ModuleClass = Mep.require(classPath);
} catch (e) {
console.log(e);
}
// Resolve dependencies
if (moduleConfig['@dependencies'] !== undefined) {
try {
await this._resolveDependencies(moduleConfig['@dependencies']);
await this._loadDriverBasic(ModuleClass, moduleConfig, driverIdentifier, classPath);
} catch (e) {
throw Error('Cannot load driver `' + driverIdentifier + '` because dependencies are out of order');
}
} else {
await this._loadDriverBasic(ModuleClass, moduleConfig, driverIdentifier, classPath);
}
}
}
async _loadDriverBasic(ModuleClass, moduleConfig, driverIdentifier, classPath) {
if (typeof ModuleClass === 'function') {
let driverInstance;
try {
driverInstance = new ModuleClass(driverIdentifier, moduleConfig);
this.drivers[driverIdentifier] = driverInstance;
if (typeof driverInstance.init === 'function') {
await driverInstance.init();
Mep.Log.debug(TAG, 'Driver `' + driverIdentifier + '` initialized');
} else {
Mep.Log.debug(TAG, 'Driver `' + driverIdentifier + '` created');
}
// Test if all methods are OK
DriverChecker.check(driverInstance);
} catch (error) {
console.log(TAG, error);
this.putDriverOutOfOrder(driverIdentifier, error);
}
} else {
Mep.Log.error(TAG, 'There is no driver', classPath);
}
}
async _resolveDependencies(dependencies) {
for (let key in dependencies) {
if (this.isDriverAvailable(dependencies[key]) === true) {
continue;
}
if (this.isDriverOutOfOrder(dependencies[key]) === true) {
this.putDriverOutOfOrder(dependencies[key],
'Cannot resolve dependency: ' + dependencies[key]);
throw Error('Dependency `' + dependencies[key] + '` driver cannot be loaded');
}
await this._initDriver(dependencies[key]);
}
}
}
module.exports = DriverManager;