signalk-racer 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReleaseNotes.md +7 -0
- package/index.js +91 -80
- package/package.json +1 -1
package/ReleaseNotes.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 0.9.10 Updates for KIP widgets
|
|
2
|
+
+ Do not set a start time unless timer is running
|
|
3
|
+
+ Schema RFC 3339 units for start time.
|
|
4
|
+
|
|
5
|
+
## 0.9.9 Working with KIP prototypes
|
|
6
|
+
+ Fixed API handlers to use return status rather than callback
|
|
7
|
+
|
|
1
8
|
## 0.9.8
|
|
2
9
|
+ Minor updates to webapp UI
|
|
3
10
|
|
package/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
module.exports = (app) => {
|
|
3
2
|
const state = {};
|
|
4
3
|
const geolib = require('geolib')
|
|
@@ -51,6 +50,7 @@ module.exports = (app) => {
|
|
|
51
50
|
{
|
|
52
51
|
"path": "navigation.racing.startLineLength",
|
|
53
52
|
"value": {
|
|
53
|
+
"type": "number",
|
|
54
54
|
"units": "m",
|
|
55
55
|
"description": "Length of the start line",
|
|
56
56
|
"displayName": "Length of the start line",
|
|
@@ -60,6 +60,7 @@ module.exports = (app) => {
|
|
|
60
60
|
{
|
|
61
61
|
"path": "navigation.racing.stbLineBias",
|
|
62
62
|
"value": {
|
|
63
|
+
"type": "number",
|
|
63
64
|
"units": "m",
|
|
64
65
|
"description": "Bias of the start line for the starboard end",
|
|
65
66
|
"displayName": "Bias of the start line for the starboard end",
|
|
@@ -69,6 +70,7 @@ module.exports = (app) => {
|
|
|
69
70
|
{
|
|
70
71
|
"path": "navigation.racing.nextLegHeading",
|
|
71
72
|
"value": {
|
|
73
|
+
"type": "number",
|
|
72
74
|
"units": "rad",
|
|
73
75
|
"description": "Heading of the next leg of the course",
|
|
74
76
|
"displayName": "Next Heading",
|
|
@@ -78,12 +80,26 @@ module.exports = (app) => {
|
|
|
78
80
|
{
|
|
79
81
|
"path": "navigation.racing.nextLegTrueWindAngle",
|
|
80
82
|
"value": {
|
|
83
|
+
"type": "number",
|
|
81
84
|
"units": "rad",
|
|
82
85
|
"description": "TWA on the next leg of the course",
|
|
83
86
|
"displayName": "Next TWA",
|
|
84
87
|
"shortName": "NextTWA",
|
|
85
88
|
}
|
|
86
89
|
},
|
|
90
|
+
{
|
|
91
|
+
"path": "navigation.racing.startTime",
|
|
92
|
+
"value": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"units": "RFC 3339 (UTC)",
|
|
95
|
+
"example": "2014-04-10T08:33:53.123Z",
|
|
96
|
+
"format": "date-time",
|
|
97
|
+
"pattern" : ".*Z$",
|
|
98
|
+
"description": "Time of the race start in RFC 3339 UTC only format ",
|
|
99
|
+
"displayName": "Start Time",
|
|
100
|
+
"shortName": "StartTime",
|
|
101
|
+
}
|
|
102
|
+
},
|
|
87
103
|
]
|
|
88
104
|
}
|
|
89
105
|
]
|
|
@@ -136,10 +152,13 @@ module.exports = (app) => {
|
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
function toOtherEnd(end) {
|
|
139
|
-
switch(end) {
|
|
140
|
-
case 'port' :
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
switch (end) {
|
|
156
|
+
case 'port' :
|
|
157
|
+
return 'stb';
|
|
158
|
+
case 'stb' :
|
|
159
|
+
return 'port';
|
|
160
|
+
default:
|
|
161
|
+
return null;
|
|
143
162
|
}
|
|
144
163
|
}
|
|
145
164
|
|
|
@@ -170,9 +189,9 @@ module.exports = (app) => {
|
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
function complete(callback, statusCode, message, details) {
|
|
173
|
-
const result = {state: '
|
|
192
|
+
const result = {state: statusCode === 200 ? 'SUCCESS' : 'FAILURE', statusCode, message, details};
|
|
174
193
|
app.debug(JSON.stringify(result));
|
|
175
|
-
|
|
194
|
+
return result;
|
|
176
195
|
}
|
|
177
196
|
|
|
178
197
|
// Put an absolute position
|
|
@@ -180,30 +199,26 @@ module.exports = (app) => {
|
|
|
180
199
|
app.debug(`putStartLineEnd: ${end} ${JSON.stringify(position)}`);
|
|
181
200
|
|
|
182
201
|
try {
|
|
183
|
-
if (!end || !position || typeof position.latitude !== 'number' || typeof position.longitude !== 'number'
|
|
184
|
-
complete(callback, 400, 'Failed to put start line end: !end || !position');
|
|
185
|
-
return;
|
|
202
|
+
if (!end || !position || typeof position.latitude !== 'number' || typeof position.longitude !== 'number') {
|
|
203
|
+
return complete(callback, 400, 'Failed to put start line end: !end || !position');
|
|
186
204
|
}
|
|
187
205
|
|
|
188
206
|
end = end.toLowerCase();
|
|
189
207
|
if (end !== 'port' && end !== 'stb') {
|
|
190
|
-
complete(callback, 400, 'Failed to put start line end: unknown end');
|
|
191
|
-
return;
|
|
208
|
+
return complete(callback, 400, 'Failed to put start line end: unknown end');
|
|
192
209
|
}
|
|
193
210
|
|
|
194
211
|
let startLine = state.startLine;
|
|
195
212
|
if (startLine) {
|
|
196
213
|
// We have a line, check if this end is the same?
|
|
197
214
|
if (startLine[end].latitude === position.latitude && startLine[end].longitude === position.longitude) {
|
|
198
|
-
complete(callback, 304, 'Put start line end: unchanged line end');
|
|
199
|
-
return;
|
|
215
|
+
return complete(callback, 304, 'Put start line end: unchanged line end');
|
|
200
216
|
}
|
|
201
217
|
} else {
|
|
202
218
|
// We don't have a line, so check if this is just an update for one end of the line?
|
|
203
219
|
const thisEnd = app.getSelfPath(`navigation.racing.startLine${camelCase(end)}`);
|
|
204
220
|
if (thisEnd && thisEnd.value.lat === position.latitude && thisEnd.value.longitude === position.longitude) {
|
|
205
|
-
complete(callback, 304, 'Put start line end: unchanged end');
|
|
206
|
-
return;
|
|
221
|
+
return complete(callback, 304, 'Put start line end: unchanged end');
|
|
207
222
|
}
|
|
208
223
|
|
|
209
224
|
// If we now have both ends, we have a line!
|
|
@@ -237,7 +252,7 @@ module.exports = (app) => {
|
|
|
237
252
|
|
|
238
253
|
// Set/create the waypoint if we are configured to do so
|
|
239
254
|
if (state.options.updateStartLineWaypoint || updateWaypoint) {
|
|
240
|
-
const waypointConfig
|
|
255
|
+
const waypointConfig = `startLine${camelCase(end)}`;
|
|
241
256
|
app.debug(`waypointConfig: ${waypointConfig}`);
|
|
242
257
|
const waypointName = state.options[waypointConfig];
|
|
243
258
|
app.debug(`waypointName: ${waypointName}`);
|
|
@@ -268,6 +283,11 @@ module.exports = (app) => {
|
|
|
268
283
|
coordinates: [position.longitude, position.latitude]
|
|
269
284
|
},
|
|
270
285
|
properties: {}
|
|
286
|
+
},
|
|
287
|
+
type: end === 'port' ? 'start-pin' : 'start-boat',
|
|
288
|
+
position: {
|
|
289
|
+
latitude: position.latitude,
|
|
290
|
+
longitude: position.longitude
|
|
271
291
|
}
|
|
272
292
|
}
|
|
273
293
|
|
|
@@ -283,36 +303,33 @@ module.exports = (app) => {
|
|
|
283
303
|
app.debug('startline waypoint not updated');
|
|
284
304
|
}
|
|
285
305
|
|
|
286
|
-
// Complete the handler successfully
|
|
287
|
-
complete(callback, 200, 'Put start line end: OK');
|
|
288
306
|
|
|
289
307
|
// If we have a startLine, then process position against the new line
|
|
290
308
|
if (startLine)
|
|
291
309
|
processPosition(null);
|
|
310
|
+
// Complete the handler successfully
|
|
311
|
+
return complete(callback, 200, 'Put start line end: OK');
|
|
292
312
|
} catch (err) {
|
|
293
|
-
complete(callback, 500,
|
|
313
|
+
return complete(callback, 500, 'Put start line end: Failed ' + JSON.stringify(err));
|
|
294
314
|
}
|
|
295
|
-
|
|
296
|
-
return { state: 'PENDING' }
|
|
297
315
|
}
|
|
298
316
|
|
|
299
317
|
async function setStartLine(context, path, args, callback) {
|
|
300
318
|
try {
|
|
301
319
|
app.debug('setStartLine:', JSON.stringify(args));
|
|
302
320
|
if (!args || !args.end || typeof args.end !== 'string') {
|
|
303
|
-
complete(callback, 400,
|
|
304
|
-
return;
|
|
321
|
+
return complete(callback, 400, 'Failed to set the startLine: !end');
|
|
305
322
|
}
|
|
306
323
|
const end = args.end.toLowerCase();
|
|
307
324
|
if (end !== 'port' && end !== 'stb') {
|
|
308
|
-
complete(callback, 400,
|
|
309
|
-
return;
|
|
325
|
+
return complete(callback, 400, 'Failed to set the startLine: unknown end');
|
|
310
326
|
}
|
|
311
327
|
|
|
312
328
|
// Get the position either as arg or as the current bow position
|
|
313
329
|
let position = args.position;
|
|
314
|
-
if (position === 'bow') {
|
|
330
|
+
if (position === 'bow' || !position && !args.delta && !args.rotate ) {
|
|
315
331
|
position = app.getSelfPath('navigation.position');
|
|
332
|
+
app.debug('bowPosition:' + JSON.stringify(position));
|
|
316
333
|
if (position)
|
|
317
334
|
position = bowPosition(position.value);
|
|
318
335
|
}
|
|
@@ -332,7 +349,7 @@ module.exports = (app) => {
|
|
|
332
349
|
app.debug(`initialBearing: ${initialBearing} currentDistance: ${currentDistance}`);
|
|
333
350
|
|
|
334
351
|
const newDistance = args.delta && typeof args.delta === 'number' ? (currentDistance + args.delta) : currentDistance;
|
|
335
|
-
const newBearing = args.rotate && typeof args.rotate === 'number'? (initialBearing + toDegrees(args.rotate)) : initialBearing;
|
|
352
|
+
const newBearing = args.rotate && typeof args.rotate === 'number' ? (initialBearing + toDegrees(args.rotate)) : initialBearing;
|
|
336
353
|
app.debug(`newDistance: ${newDistance} newBearing: ${newBearing}`);
|
|
337
354
|
|
|
338
355
|
const newPosition = geolib.computeDestinationPoint(otherEnd, newDistance, newBearing);
|
|
@@ -342,11 +359,10 @@ module.exports = (app) => {
|
|
|
342
359
|
|
|
343
360
|
if (position)
|
|
344
361
|
return putStartLineEnd(end, position, callback, args.updateWaypoint === true);
|
|
345
|
-
complete(callback, 500,
|
|
362
|
+
return complete(callback, 500, 'Failed to set the startLine: No position');
|
|
346
363
|
} catch (err) {
|
|
347
|
-
complete(callback, 500,
|
|
364
|
+
return complete(callback, 500, 'Failed to set the startLine: ' + JSON.stringify(err));
|
|
348
365
|
}
|
|
349
|
-
return { state: 'PENDING' }
|
|
350
366
|
}
|
|
351
367
|
|
|
352
368
|
function setTimer() {
|
|
@@ -358,7 +374,7 @@ module.exports = (app) => {
|
|
|
358
374
|
return;
|
|
359
375
|
}
|
|
360
376
|
state.timeToStart--;
|
|
361
|
-
sendDeltas([{
|
|
377
|
+
sendDeltas([{path: 'navigation.racing.timeToStart', value: state.timeToStart}]);
|
|
362
378
|
}, 1000);
|
|
363
379
|
}
|
|
364
380
|
|
|
@@ -367,13 +383,11 @@ module.exports = (app) => {
|
|
|
367
383
|
try {
|
|
368
384
|
app.debug('startTimerCommand:', JSON.stringify(args));
|
|
369
385
|
if (!args || !args.command || typeof args.command !== 'string') {
|
|
370
|
-
complete(callback, 400,
|
|
371
|
-
return;
|
|
386
|
+
return complete(callback, 400, 'Failed to command timer: no command');
|
|
372
387
|
}
|
|
373
388
|
const command = args.command.toLowerCase();
|
|
374
389
|
|
|
375
|
-
switch (command)
|
|
376
|
-
{
|
|
390
|
+
switch (command) {
|
|
377
391
|
case 'start': {
|
|
378
392
|
const now = new Date();
|
|
379
393
|
const timeToStart = state.timeToStart ?? state.options.timer ?? 300;
|
|
@@ -389,8 +403,7 @@ module.exports = (app) => {
|
|
|
389
403
|
]);
|
|
390
404
|
|
|
391
405
|
setTimer();
|
|
392
|
-
complete(callback, 200,
|
|
393
|
-
break;
|
|
406
|
+
return complete(callback, 200, 'start timer: OK');
|
|
394
407
|
}
|
|
395
408
|
case 'reset': {
|
|
396
409
|
state.timerRunning = false;
|
|
@@ -401,14 +414,12 @@ module.exports = (app) => {
|
|
|
401
414
|
{path: 'navigation.racing.timeToStart', value: state.timeToStart},
|
|
402
415
|
{path: 'navigation.racing.startTime', value: null}
|
|
403
416
|
]);
|
|
404
|
-
complete(callback, 200,
|
|
405
|
-
break;
|
|
417
|
+
return complete(callback, 200, 'reset timer: OK');
|
|
406
418
|
}
|
|
407
419
|
|
|
408
420
|
case 'sync': {
|
|
409
421
|
if (!state.timerRunning || state.timeToStart == null) {
|
|
410
|
-
complete(callback, 500,
|
|
411
|
-
return;
|
|
422
|
+
return complete(callback, 500, 'sync timer: !running');
|
|
412
423
|
}
|
|
413
424
|
|
|
414
425
|
const rounded = Math.round(state.timeToStart / 60) * 60;
|
|
@@ -416,24 +427,21 @@ module.exports = (app) => {
|
|
|
416
427
|
state.startTime = new Date(Date.now() + rounded * 1000).toISOString();
|
|
417
428
|
|
|
418
429
|
sendDeltas([
|
|
419
|
-
{
|
|
420
|
-
{
|
|
430
|
+
{path: 'navigation.racing.timeToStart', value: state.timeToStart},
|
|
431
|
+
{path: 'navigation.racing.startTime', value: state.startTime}
|
|
421
432
|
]);
|
|
422
|
-
complete(callback, 200,
|
|
423
|
-
break;
|
|
433
|
+
return complete(callback, 200, 'sync timer: OK');
|
|
424
434
|
}
|
|
425
435
|
|
|
426
436
|
case 'set': {
|
|
427
437
|
const input = args.startTime;
|
|
428
438
|
if (!input) {
|
|
429
|
-
complete(callback, 400,
|
|
430
|
-
return;
|
|
439
|
+
return complete(callback, 400, 'reset timer: !startTime');
|
|
431
440
|
}
|
|
432
441
|
|
|
433
442
|
const inputTime = new Date(input);
|
|
434
443
|
if (isNaN(inputTime)) {
|
|
435
|
-
complete(callback, 400,
|
|
436
|
-
return;
|
|
444
|
+
return complete(callback, 400, 'reset timer: invalid startTime');
|
|
437
445
|
}
|
|
438
446
|
|
|
439
447
|
const now = new Date();
|
|
@@ -444,11 +452,10 @@ module.exports = (app) => {
|
|
|
444
452
|
state.timeToStart = 0;
|
|
445
453
|
state.startTime = null;
|
|
446
454
|
sendDeltas([
|
|
447
|
-
{
|
|
448
|
-
{
|
|
455
|
+
{path: 'navigation.racing.timeToStart', value: 0},
|
|
456
|
+
{path: 'navigation.racing.startTime', value: null}
|
|
449
457
|
]);
|
|
450
|
-
complete(callback, 200,
|
|
451
|
-
return;
|
|
458
|
+
return complete(callback, 200, 'set timer: 0K');
|
|
452
459
|
}
|
|
453
460
|
|
|
454
461
|
state.timerRunning = true;
|
|
@@ -456,46 +463,47 @@ module.exports = (app) => {
|
|
|
456
463
|
state.startTime = inputTime.toISOString();
|
|
457
464
|
|
|
458
465
|
sendDeltas([
|
|
459
|
-
{
|
|
460
|
-
{
|
|
466
|
+
{path: 'navigation.racing.timeToStart', value: state.timeToStart},
|
|
467
|
+
{path: 'navigation.racing.startTime', value: state.startTime}
|
|
461
468
|
]);
|
|
462
469
|
|
|
463
470
|
setTimer();
|
|
464
|
-
complete(callback, 200,
|
|
465
|
-
break;
|
|
471
|
+
return complete(callback, 200, 'set timer: OK');
|
|
466
472
|
}
|
|
467
473
|
|
|
468
474
|
case 'adjust': {
|
|
469
475
|
const delta = Number(args.delta);
|
|
470
476
|
if (isNaN(delta) || state.timeToStart == null || !state.startTime) {
|
|
471
|
-
cb(null, {
|
|
477
|
+
cb(null, {state: 'FAILED', message: 'Cannot adjust: invalid state or delta'});
|
|
472
478
|
return;
|
|
473
479
|
}
|
|
474
480
|
|
|
475
|
-
state.timeToStart
|
|
476
|
-
if (state.timeToStart < 0) state.timeToStart = 0;
|
|
481
|
+
state.timeToStart = Math.max(state.timeToStart + delta, 0);
|
|
477
482
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
483
|
+
if (state.timerRunning) {
|
|
484
|
+
let now = Date.now();
|
|
485
|
+
state.startTime = new Date(now - (now % 1000) + state.timeToStart * 1000).toISOString();
|
|
486
|
+
}
|
|
481
487
|
|
|
482
|
-
|
|
483
|
-
{ path: 'navigation.racing.timeToStart', value: state.timeToStart }
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
+
const deltas = [
|
|
489
|
+
{ path: 'navigation.racing.timeToStart', value: state.timeToStart }
|
|
490
|
+
];
|
|
491
|
+
if (state.timerRunning && state.startTime) {
|
|
492
|
+
deltas.push({path: 'navigation.racing.startTime', value: state.startTime });
|
|
493
|
+
}
|
|
494
|
+
sendDeltas(deltas);
|
|
495
|
+
|
|
496
|
+
return complete(callback, 200, 'adjust timer: OK');
|
|
488
497
|
}
|
|
489
498
|
|
|
490
499
|
default : {
|
|
491
|
-
complete(callback, 400,
|
|
492
|
-
return;
|
|
500
|
+
return complete(callback, 400, 'Failed to command timer: unknown command');
|
|
493
501
|
}
|
|
494
502
|
}
|
|
495
503
|
} catch (err) {
|
|
496
|
-
|
|
504
|
+
app.debug('startTimerCommand:', JSON.stringify(err));
|
|
505
|
+
return complete(callback, 500, 'Failed to command timer: ' + JSON.stringify(err));
|
|
497
506
|
}
|
|
498
|
-
return { state: 'PENDING' }
|
|
499
507
|
}
|
|
500
508
|
|
|
501
509
|
function findLineAndThenProcess(position, alwaysFindLine = false) {
|
|
@@ -613,8 +621,7 @@ module.exports = (app) => {
|
|
|
613
621
|
sendDeltas([
|
|
614
622
|
{path: 'navigation.racing.distanceStartline', value: distanceToLine},
|
|
615
623
|
]);
|
|
616
|
-
}
|
|
617
|
-
else if (state.distanceToLine) {
|
|
624
|
+
} else if (state.distanceToLine) {
|
|
618
625
|
state.distanceToLine = null;
|
|
619
626
|
sendDeltas([
|
|
620
627
|
{path: 'navigation.racing.distanceStartline', value: null},
|
|
@@ -841,8 +848,8 @@ module.exports = (app) => {
|
|
|
841
848
|
{
|
|
842
849
|
context: 'vessels.' + app.selfId,
|
|
843
850
|
subscribe: [
|
|
844
|
-
{
|
|
845
|
-
{
|
|
851
|
+
{path: 'navigation.racing.startLinePort', policy: 'instant'},
|
|
852
|
+
{path: 'navigation.racing.startLineStb', policy: 'instant'},
|
|
846
853
|
]
|
|
847
854
|
},
|
|
848
855
|
unsubscribes,
|
|
@@ -857,10 +864,14 @@ module.exports = (app) => {
|
|
|
857
864
|
if (u.source && u.source.label && u.source.label !== 'signalk-racer') {
|
|
858
865
|
switch (v.path) {
|
|
859
866
|
case 'navigation.racing.startLinePort' :
|
|
860
|
-
putStartLineEnd('port', v.value, ignored => {
|
|
867
|
+
putStartLineEnd('port', v.value, ignored => {
|
|
868
|
+
}, false).then(ignored => {
|
|
869
|
+
});
|
|
861
870
|
break;
|
|
862
871
|
case 'navigation.racing.startLineStb' :
|
|
863
|
-
putStartLineEnd('port', v.value, ignored => {
|
|
872
|
+
putStartLineEnd('port', v.value, ignored => {
|
|
873
|
+
}, false).then(ignored => {
|
|
874
|
+
});
|
|
864
875
|
break;
|
|
865
876
|
}
|
|
866
877
|
}
|