signalk-racer 0.9.12 → 1.0.0
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/README.md +9 -7
- package/ReleaseNotes.md +8 -0
- package/index.js +125 -50
- package/package.json +1 -1
- package/public/index.html +34 -34
- package/racer-kip.png +0 -0
- package/kip-freeboard-screenshot.png +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Signal K Racing Plugin
|
|
2
2
|
|
|
3
|
-
This plugin adds sail racing
|
|
3
|
+
This plugin adds sail racing relevant data to a Signal K server. It focuses on real-time information useful during the start of a race, such as:
|
|
4
4
|
+ A server side race timer that can be displayed on multiple screens and be finely adjusted
|
|
5
5
|
+ A mechanism to set and adjust the start line using waypoints and REST commands
|
|
6
6
|
+ Calculation of the distance to the line.
|
|
@@ -26,9 +26,10 @@ This plugin calculates and publishes the following Signal K paths:
|
|
|
26
26
|
| `navigation.racing.`<br/>`timeToStart` | Period of time until the race start | `s` | Y |
|
|
27
27
|
| `navigation.racing.`<br/>`startTime` | The start time as an ISO timestamp | `rfc3339` | |
|
|
28
28
|
|
|
29
|
-
These values can be displayed in KIP widgets, Freeboard-SK, or other Signal K clients.
|
|
29
|
+
These values can be displayed in KIP widgets, Freeboard-SK, or other Signal K clients.
|
|
30
|
+
There are dedicated widgets for the start timer and line adjustment since 3.5.0 of KIP
|
|
30
31
|
|
|
31
|
-

|
|
32
33
|
|
|
33
34
|
There is also a webapp to adjust the line and timer.
|
|
34
35
|
|
|
@@ -95,7 +96,7 @@ If `navigation.headingTrue` is not available, then `navigation.courseOverGroundT
|
|
|
95
96
|
|
|
96
97
|
## 🌐 API Access
|
|
97
98
|
|
|
98
|
-
This plugin uses WebSocket-based PUT requests to Signal K model paths.
|
|
99
|
+
This plugin uses the following WebSocket-based PUT requests to Signal K model paths.
|
|
99
100
|
|
|
100
101
|
### `navigation.racing.setStartLine`
|
|
101
102
|
|
|
@@ -133,13 +134,15 @@ Used to **start**, **sync**, **reset**, or **set a fixed start time**.
|
|
|
133
134
|
---
|
|
134
135
|
|
|
135
136
|
## 🧩 KIP Widgets
|
|
136
|
-
KIP components for `racer-timer` and `racer-line` have been developed and
|
|
137
|
+
KIP components for `racer-timer` and `racer-line` have been developed and are available from release 3.5.0 of KIP.
|
|
137
138
|
|
|
138
139
|
## 🔄 Dependencies
|
|
139
140
|
|
|
140
141
|
### Plugins
|
|
142
|
+
- [KIP >= 3.5.0](https://github.com/mxtommy/Kip) for graphic widget support
|
|
143
|
+
- [FreeboardSK](https://github.com/SignalK/freeboard-sk#readme) for graphic display of the start line
|
|
141
144
|
- [resources-provider](https://www.npmjs.com/package/@signalk/resources-provider) plugin with waypoints enabled
|
|
142
|
-
- [course-provider](https://www.npmjs.com/package/@signalk/course-provider) plugin
|
|
145
|
+
- [course-provider](https://www.npmjs.com/package/@signalk/course-provider) plugin to enable the next leg calculations
|
|
143
146
|
|
|
144
147
|
### Libraries
|
|
145
148
|
- `geolib`: for distance and bearing calculations.
|
|
@@ -151,7 +154,6 @@ KIP components for `racer-timer` and `racer-line` have been developed and will b
|
|
|
151
154
|
- Add calculations for:
|
|
152
155
|
- **Time to line** (using STW and heading or polar speeds)
|
|
153
156
|
- **Laylines** and distance/time to them
|
|
154
|
-
- Better integration with **Freeboard-SK** and **KIP** dashboards
|
|
155
157
|
|
|
156
158
|
---
|
|
157
159
|
## 📬 Feedback
|
package/ReleaseNotes.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 1.0.0 GA Release with KIP Widgets
|
|
2
|
+
+ Fixed bias to be towards wind
|
|
3
|
+
|
|
4
|
+
## 0.9.13 GA Release Candidate
|
|
5
|
+
+ handle reverse routes
|
|
6
|
+
+ Improve startAt date handling
|
|
7
|
+
+ persist timer over plugin restart
|
|
8
|
+
|
|
1
9
|
## 0.9.12 No waypoints
|
|
2
10
|
+ improve no waypoint handling
|
|
3
11
|
+ fix create waypoint
|
package/index.js
CHANGED
|
@@ -105,7 +105,7 @@ module.exports = (app) => {
|
|
|
105
105
|
]
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
// send multiple deltas, each in the form of { path, value
|
|
108
|
+
// send multiple deltas, each in the form of { path, value }
|
|
109
109
|
function sendDeltas(updatesArray) {
|
|
110
110
|
const timestamp = new Date().toISOString();
|
|
111
111
|
|
|
@@ -199,14 +199,12 @@ module.exports = (app) => {
|
|
|
199
199
|
app.debug(`putStartLineEnd: ${end} ${JSON.stringify(position)}`);
|
|
200
200
|
|
|
201
201
|
try {
|
|
202
|
-
if (!end || !position || typeof position.latitude !== 'number' || typeof position.longitude !== 'number')
|
|
202
|
+
if (!end || !position || typeof position.latitude !== 'number' || typeof position.longitude !== 'number')
|
|
203
203
|
return complete(callback, 400, 'Failed to put start line end: !end || !position');
|
|
204
|
-
}
|
|
205
204
|
|
|
206
205
|
end = end.toLowerCase();
|
|
207
|
-
if (end !== 'port' && end !== 'stb')
|
|
206
|
+
if (end !== 'port' && end !== 'stb')
|
|
208
207
|
return complete(callback, 400, 'Failed to put start line end: unknown end');
|
|
209
|
-
}
|
|
210
208
|
|
|
211
209
|
let startLine = state.startLine;
|
|
212
210
|
if (startLine) {
|
|
@@ -427,9 +425,8 @@ module.exports = (app) => {
|
|
|
427
425
|
}
|
|
428
426
|
|
|
429
427
|
case 'sync': {
|
|
430
|
-
if (!state.timerRunning || state.timeToStart == null)
|
|
428
|
+
if (!state.timerRunning || state.timeToStart == null)
|
|
431
429
|
return complete(callback, 500, 'sync timer: !running');
|
|
432
|
-
}
|
|
433
430
|
|
|
434
431
|
const rounded = Math.round(state.timeToStart / 60) * 60;
|
|
435
432
|
state.timeToStart = rounded;
|
|
@@ -465,7 +462,7 @@ module.exports = (app) => {
|
|
|
465
462
|
{path: 'navigation.racing.timeToStart', value: 0},
|
|
466
463
|
{path: 'navigation.racing.startTime', value: null}
|
|
467
464
|
]);
|
|
468
|
-
return complete(callback, 200, 'set timer:
|
|
465
|
+
return complete(callback, 200, 'set timer: Ignored negative start time');
|
|
469
466
|
}
|
|
470
467
|
|
|
471
468
|
state.timerRunning = true;
|
|
@@ -483,12 +480,17 @@ module.exports = (app) => {
|
|
|
483
480
|
|
|
484
481
|
case 'adjust': {
|
|
485
482
|
const delta = Number(args.delta);
|
|
486
|
-
if (isNaN(delta)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
483
|
+
if (isNaN(delta))
|
|
484
|
+
return complete(callback, 400, 'adjust timer: Cannot adjust invalid delta');
|
|
485
|
+
|
|
486
|
+
if (state.timeToStart == null)
|
|
487
|
+
return complete(callback, 400, 'adjust timer: Cannot adjust start time');
|
|
490
488
|
|
|
491
|
-
|
|
489
|
+
const nextTimeToStart = state.timeToStart + delta;
|
|
490
|
+
if (nextTimeToStart <= 0)
|
|
491
|
+
return complete(callback, 200, 'adjust timer: Ignored negative start time');
|
|
492
|
+
|
|
493
|
+
state.timeToStart = nextTimeToStart;
|
|
492
494
|
|
|
493
495
|
if (state.timerRunning) {
|
|
494
496
|
let now = Date.now();
|
|
@@ -501,7 +503,9 @@ module.exports = (app) => {
|
|
|
501
503
|
if (state.timerRunning && state.startTime) {
|
|
502
504
|
deltas.push({path: 'navigation.racing.startTime', value: state.startTime });
|
|
503
505
|
}
|
|
504
|
-
|
|
506
|
+
if (delta % 60 !== 0)
|
|
507
|
+
setTimer();
|
|
508
|
+
|
|
505
509
|
sendDeltas(deltas);
|
|
506
510
|
|
|
507
511
|
return complete(callback, 200, 'adjust timer: OK');
|
|
@@ -512,8 +516,8 @@ module.exports = (app) => {
|
|
|
512
516
|
}
|
|
513
517
|
}
|
|
514
518
|
} catch (err) {
|
|
515
|
-
app.debug('startTimerCommand:',
|
|
516
|
-
return complete(callback, 500, 'Failed to command timer: ' +
|
|
519
|
+
app.debug('startTimerCommand:', err);
|
|
520
|
+
return complete(callback, 500, 'Failed to command timer: ' + err.message);
|
|
517
521
|
}
|
|
518
522
|
}
|
|
519
523
|
|
|
@@ -577,6 +581,23 @@ module.exports = (app) => {
|
|
|
577
581
|
}
|
|
578
582
|
}
|
|
579
583
|
|
|
584
|
+
function isNearLineInRoute() {
|
|
585
|
+
// e.g. {
|
|
586
|
+
// "href":"/resources/routes/a12eefe7-8fd3-49f7-81af-ce1f3440d3ff",
|
|
587
|
+
// "name":"Course 1",
|
|
588
|
+
// "reverse":false,
|
|
589
|
+
// "pointIndex":1,
|
|
590
|
+
// "pointTotal":5
|
|
591
|
+
// }
|
|
592
|
+
|
|
593
|
+
const activeRoute = state.activeRoute;
|
|
594
|
+
if (!activeRoute)
|
|
595
|
+
return true;
|
|
596
|
+
if (activeRoute.pointIndex < 2)
|
|
597
|
+
return true;
|
|
598
|
+
return activeRoute.pointIndex + 2 >= activeRoute.pointTotal;
|
|
599
|
+
}
|
|
600
|
+
|
|
580
601
|
function processPosition(position) {
|
|
581
602
|
if (!position) {
|
|
582
603
|
position = app.getSelfPath('navigation.position');
|
|
@@ -587,7 +608,7 @@ module.exports = (app) => {
|
|
|
587
608
|
const startLine = state.startLine;
|
|
588
609
|
|
|
589
610
|
app.debug(`processPosition ${JSON.stringify(position)} to ${JSON.stringify(startLine)}`);
|
|
590
|
-
if (position && startLine && (
|
|
611
|
+
if (position && startLine && isNearLineInRoute()) {
|
|
591
612
|
// handle the bow offset
|
|
592
613
|
const bow = bowPosition(position);
|
|
593
614
|
|
|
@@ -636,6 +657,7 @@ module.exports = (app) => {
|
|
|
636
657
|
state.distanceToLine = null;
|
|
637
658
|
sendDeltas([
|
|
638
659
|
{path: 'navigation.racing.distanceStartline', value: null},
|
|
660
|
+
{path: 'navigation.racing.startLineBias', value: null},
|
|
639
661
|
]);
|
|
640
662
|
}
|
|
641
663
|
}
|
|
@@ -652,19 +674,16 @@ module.exports = (app) => {
|
|
|
652
674
|
|
|
653
675
|
let nextTwa = null;
|
|
654
676
|
const activeRoute = state.activeRoute;
|
|
655
|
-
if (activeRoute) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
nextTwa = (540 + toDegrees(twd) - nextLegHeading) % 360 - 180;
|
|
659
|
-
app.debug(`nextTwa: ${nextTwa}`);
|
|
660
|
-
}
|
|
677
|
+
if (activeRoute && activeRoute.nextLegHeading) {
|
|
678
|
+
nextTwa = (540 + toDegrees(twd) - activeRoute.nextLegHeading) % 360 - 180;
|
|
679
|
+
app.debug(`nextTwa: ${nextTwa}`);
|
|
661
680
|
}
|
|
662
681
|
|
|
663
682
|
let bias = null;
|
|
664
683
|
const startLine = state.startLine;
|
|
665
|
-
if (startLine && (
|
|
684
|
+
if (startLine && isNearLineInRoute()) {
|
|
666
685
|
app.debug(`processWind ${twd} to ${JSON.stringify(startLine)}`);
|
|
667
|
-
bias = startLine.length * Math.cos(toRadians(startLine.bearing) -
|
|
686
|
+
bias = - startLine.length * Math.cos(toRadians(startLine.bearing) - twd);
|
|
668
687
|
}
|
|
669
688
|
|
|
670
689
|
sendDeltas([
|
|
@@ -674,16 +693,50 @@ module.exports = (app) => {
|
|
|
674
693
|
}
|
|
675
694
|
|
|
676
695
|
function processRoute(activeRoute) {
|
|
677
|
-
if (activeRoute
|
|
696
|
+
if (activeRoute) {
|
|
678
697
|
// e.g. {"href":"/resources/routes/a12eefe7-8fd3-49f7-81af-ce1f3440d3ff","name":"Course 1","reverse":false,"pointIndex":1,"pointTotal":5}
|
|
698
|
+
const nextPointIndex = activeRoute.pointIndex + 1;
|
|
699
|
+
if (nextPointIndex < 0 || nextPointIndex === activeRoute.pointTotal) {
|
|
700
|
+
activeRoute.nextLegHeading = null;
|
|
701
|
+
sendDeltas([
|
|
702
|
+
{path: 'navigation.racing.nextLegHeading', value: null},
|
|
703
|
+
{path: 'navigation.racing.nextLegTrueWindAngle', value: null}
|
|
704
|
+
]);
|
|
705
|
+
processPosition();
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
679
709
|
const routeId = activeRoute.href.split('/').pop(); // e.g. "a12eefe7-8fd3-49f7-81af-ce1f3440d3ff"
|
|
680
710
|
app.debug('processRoute:' + routeId);
|
|
681
711
|
app.resourcesApi.getResource('routes', routeId).then(route => {
|
|
682
712
|
app.debug('found active route:', JSON.stringify(route));
|
|
683
|
-
// e.g. {
|
|
713
|
+
// e.g. {
|
|
714
|
+
// name:"Course 1",
|
|
715
|
+
// description:"Test race course",
|
|
716
|
+
// distance:3027,
|
|
717
|
+
// feature:{
|
|
718
|
+
// type:"Feature",
|
|
719
|
+
// geometry:{
|
|
720
|
+
// type:"LineString",
|
|
721
|
+
// coordinates:[[151.27,-33.80],[151.28,-33.81],[151.27,-33.80],[151.27,-33.80],[151.27,-33.80]]},
|
|
722
|
+
// properties:{
|
|
723
|
+
// coordinatesMeta:[
|
|
724
|
+
// {href:"/resources/waypoints/bb35d9d0-c04e-4721-89f2-a1d8e697ab81"},
|
|
725
|
+
// {href:"/resources/waypoints/65082ddd-6fa6-4538-9a01-49d5d795b0bb"},
|
|
726
|
+
// {href:"/resources/waypoints/0bbce508-67c6-4f03-8de0-fb0532f88917"},
|
|
727
|
+
// {href:"/resources/waypoints/602ee562-c6dc-42c3-a775-512597063ca4"},
|
|
728
|
+
// {href:"/resources/waypoints/2e3cf194-8835-498d-abaa-a5d2104550b3"}
|
|
729
|
+
// ]
|
|
730
|
+
// },
|
|
731
|
+
// id:""
|
|
732
|
+
// },
|
|
733
|
+
// timestamp:"2025-06-09T22:22:54.739Z",
|
|
734
|
+
// $source:"resources-provider"}
|
|
684
735
|
const coordinates = route.feature.geometry.coordinates;
|
|
685
|
-
const
|
|
686
|
-
const
|
|
736
|
+
const fromIndex = activeRoute.reverse ? activeRoute.pointTotal - activeRoute.pointIndex - 1 : activeRoute.pointIndex;
|
|
737
|
+
const toIndex = activeRoute.reverse ? activeRoute.pointTotal - nextPointIndex - 1 : nextPointIndex;
|
|
738
|
+
const [fromLongitude, fromLatitude] = coordinates[fromIndex];
|
|
739
|
+
const [toLongitude, toLatitude] = coordinates[toIndex];
|
|
687
740
|
app.debug(`from: {longitude:${fromLongitude}, latitude:${fromLatitude}}, to: {longitude:${toLongitude}, latitude:${toLatitude}}`);
|
|
688
741
|
const nextLegHeading = geolib.getRhumbLineBearing(
|
|
689
742
|
{latitude: fromLatitude, longitude: fromLongitude},
|
|
@@ -693,16 +746,20 @@ module.exports = (app) => {
|
|
|
693
746
|
activeRoute.nextLegHeading = nextLegHeading;
|
|
694
747
|
state.activeRoute = activeRoute;
|
|
695
748
|
sendDeltas([
|
|
696
|
-
{path: 'navigation.racing.nextLegHeading', value: toRadians(nextLegHeading)}
|
|
749
|
+
{path: 'navigation.racing.nextLegHeading', value: toRadians(nextLegHeading)},
|
|
750
|
+
{path: 'navigation.racing.nextLegTrueWindAngle', value: null}
|
|
697
751
|
]);
|
|
752
|
+
processWind();
|
|
698
753
|
}).catch(err => {
|
|
699
754
|
app.error(err);
|
|
700
755
|
});
|
|
701
756
|
} else {
|
|
702
757
|
state.activeRoute = null;
|
|
703
758
|
sendDeltas([
|
|
704
|
-
{path: 'navigation.racing.nextLegHeading', value: null}
|
|
759
|
+
{path: 'navigation.racing.nextLegHeading', value: null},
|
|
760
|
+
{path: 'navigation.racing.nextLegTrueWindAngle', value: null}
|
|
705
761
|
]);
|
|
762
|
+
processPosition(null);
|
|
706
763
|
}
|
|
707
764
|
}
|
|
708
765
|
|
|
@@ -742,6 +799,23 @@ module.exports = (app) => {
|
|
|
742
799
|
state.timeToStart = options.timer;
|
|
743
800
|
state.startLine = getStartLine();
|
|
744
801
|
|
|
802
|
+
// Is the timer already started
|
|
803
|
+
{
|
|
804
|
+
const startTime = app.getSelfPath('navigation.racing.startTime');
|
|
805
|
+
if (startTime && startTime.value) {
|
|
806
|
+
state.startTime = startTime.value;
|
|
807
|
+
const now = Date.now();
|
|
808
|
+
const startAtTime = new Date(startTime.value);
|
|
809
|
+
if (startAtTime > now) {
|
|
810
|
+
state.timeToStart = Math.round((new Date(state.startTime) - now) / 1000);
|
|
811
|
+
state.timerRunning = true;
|
|
812
|
+
} else {
|
|
813
|
+
state.timeToStart = 0;
|
|
814
|
+
state.timerRunning = false;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
745
819
|
sendDeltas([{path: 'navigation.racing.timeToStart', value: state.timeToStart}]);
|
|
746
820
|
|
|
747
821
|
// Subscribe to position updates.
|
|
@@ -762,7 +836,7 @@ module.exports = (app) => {
|
|
|
762
836
|
|
|
763
837
|
(delta) => {
|
|
764
838
|
app.debug('DELTA POSITIONS ' + JSON.stringify(delta));
|
|
765
|
-
position = null;
|
|
839
|
+
let position = null;
|
|
766
840
|
delta.updates.forEach((u) => {
|
|
767
841
|
u.values.forEach((v) => {
|
|
768
842
|
app.debug('DELTA POSITION ' + v.path + ' = ' + JSON.stringify(v.value));
|
|
@@ -863,8 +937,7 @@ module.exports = (app) => {
|
|
|
863
937
|
app.debug("DELTA ACTIVE ROUTE: " + v.path + ' = ' + JSON.stringify(route));
|
|
864
938
|
})
|
|
865
939
|
});
|
|
866
|
-
|
|
867
|
-
processRoute(route);
|
|
940
|
+
processRoute(route);
|
|
868
941
|
}
|
|
869
942
|
);
|
|
870
943
|
|
|
@@ -884,23 +957,25 @@ module.exports = (app) => {
|
|
|
884
957
|
(delta) => {
|
|
885
958
|
app.debug('DELTAS START LINE ENDS ' + JSON.stringify(delta));
|
|
886
959
|
delta.updates.forEach((u) => {
|
|
887
|
-
u.values
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
960
|
+
if (u.values) {
|
|
961
|
+
u.values.forEach((v) => {
|
|
962
|
+
app.debug(`DELTA START LINE END from ${u.source.label}: ${v.path} = ${JSON.stringify(v.value)}`);
|
|
963
|
+
if (u.source && u.source.label && u.source.label !== 'signalk-racer') {
|
|
964
|
+
switch (v.path) {
|
|
965
|
+
case 'navigation.racing.startLinePort' :
|
|
966
|
+
putStartLineEnd('port', v.value, ignored => {
|
|
967
|
+
}, false).then(ignored => {
|
|
968
|
+
});
|
|
969
|
+
break;
|
|
970
|
+
case 'navigation.racing.startLineStb' :
|
|
971
|
+
putStartLineEnd('port', v.value, ignored => {
|
|
972
|
+
}, false).then(ignored => {
|
|
973
|
+
});
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
901
976
|
}
|
|
902
|
-
}
|
|
903
|
-
}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
904
979
|
});
|
|
905
980
|
}
|
|
906
981
|
);
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -44,11 +44,9 @@
|
|
|
44
44
|
|
|
45
45
|
.port-set { background-color: #f88; color: black; font-size: 1em;}
|
|
46
46
|
.port-move { background-color: #a00; color: white; }
|
|
47
|
-
.port-tweak { background-color: #f44; color: white; }
|
|
48
47
|
.port-rotate { background-color: #a00; color: white; }
|
|
49
48
|
.stb-set { background-color: #8f8; color: black; font-size: 1em; }
|
|
50
49
|
.stb-move { background-color: #080; color: white;}
|
|
51
|
-
.stb-tweak { background-color: #4c4; color: white; }
|
|
52
50
|
.stb-rotate { background-color: #080; color: white; }
|
|
53
51
|
|
|
54
52
|
</style>
|
|
@@ -92,9 +90,9 @@
|
|
|
92
90
|
}));
|
|
93
91
|
};
|
|
94
92
|
|
|
95
|
-
socket.onerror = (err) => updateStatus('
|
|
93
|
+
socket.onerror = (err) => updateStatus('WebSocket error: ' + err.message);
|
|
96
94
|
socket.onclose = () => {
|
|
97
|
-
updateStatus('
|
|
95
|
+
updateStatus('Disconnected. Retrying in 5s...');
|
|
98
96
|
setTimeout(connectWebSocket, 5000);
|
|
99
97
|
};
|
|
100
98
|
|
|
@@ -142,7 +140,7 @@
|
|
|
142
140
|
|
|
143
141
|
function sendSetStartLine(end) {
|
|
144
142
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
145
|
-
updateStatus('
|
|
143
|
+
updateStatus('WebSocket not connected.');
|
|
146
144
|
return;
|
|
147
145
|
}
|
|
148
146
|
let message = JSON.stringify({
|
|
@@ -154,12 +152,12 @@
|
|
|
154
152
|
});
|
|
155
153
|
console.log("sendSetStartLine: " + message);
|
|
156
154
|
socket.send(message);
|
|
157
|
-
updateStatus(
|
|
155
|
+
updateStatus(`Sent request to set ${end}`);
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
function sendAdjustStartLine(end, delta, rotate) {
|
|
161
159
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
162
|
-
updateStatus('
|
|
160
|
+
updateStatus('WebSocket not connected.');
|
|
163
161
|
return;
|
|
164
162
|
}
|
|
165
163
|
const message = JSON.stringify({
|
|
@@ -171,7 +169,7 @@
|
|
|
171
169
|
});
|
|
172
170
|
console.log("sendAdjustStartLine: " + message);
|
|
173
171
|
socket.send(message);
|
|
174
|
-
updateStatus(
|
|
172
|
+
updateStatus(`Sent ${delta}m ${rotate}° request for ${end}`);
|
|
175
173
|
}
|
|
176
174
|
|
|
177
175
|
connectWebSocket();
|
|
@@ -184,7 +182,7 @@
|
|
|
184
182
|
|
|
185
183
|
function adjustStartTime(deltaSeconds) {
|
|
186
184
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
187
|
-
updateStatus('
|
|
185
|
+
updateStatus('WebSocket not connected.');
|
|
188
186
|
return;
|
|
189
187
|
}
|
|
190
188
|
socket.send(JSON.stringify({
|
|
@@ -197,35 +195,45 @@
|
|
|
197
195
|
}
|
|
198
196
|
}
|
|
199
197
|
}));
|
|
200
|
-
updateStatus(
|
|
198
|
+
updateStatus(`Sent delta timeToStart: ${deltaSeconds} sec`);
|
|
201
199
|
}
|
|
202
200
|
|
|
203
201
|
function setStartTime() {
|
|
204
202
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
205
|
-
updateStatus('
|
|
203
|
+
updateStatus('WebSocket not connected.');
|
|
206
204
|
return;
|
|
207
205
|
}
|
|
208
206
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
try {
|
|
208
|
+
const now = new Date();
|
|
209
|
+
const parts = document.getElementById('startTime').value.split(':').map(Number);
|
|
210
|
+
const hours = parts[0];
|
|
211
|
+
const minutes = parts[1];
|
|
212
|
+
const seconds = parts.length >= 3 ? parts[2] : 0;
|
|
213
|
+
const date = new Date(now);
|
|
214
|
+
date.setHours(hours, minutes, seconds, 0);
|
|
215
|
+
if (date <= now)
|
|
216
|
+
date.setDate(date.getDate() + 1);
|
|
212
217
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
socket.send(JSON.stringify({
|
|
219
|
+
context: 'vessels.self',
|
|
220
|
+
put: {
|
|
221
|
+
path: 'navigation.racing.setStartTime',
|
|
222
|
+
value: {
|
|
223
|
+
command: 'set',
|
|
224
|
+
startTime: date.toISOString(),
|
|
225
|
+
}
|
|
220
226
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
227
|
+
}));
|
|
228
|
+
updateStatus(`Sent start time: ${startTime}`);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.log('Could not set start at ', err);
|
|
231
|
+
}
|
|
224
232
|
}
|
|
225
233
|
|
|
226
234
|
function sendTimerCommand(command) {
|
|
227
235
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
228
|
-
updateStatus('
|
|
236
|
+
updateStatus('WebSocket not connected.');
|
|
229
237
|
return;
|
|
230
238
|
}
|
|
231
239
|
socket.send(JSON.stringify({
|
|
@@ -235,16 +243,8 @@
|
|
|
235
243
|
value: { command }
|
|
236
244
|
}
|
|
237
245
|
}));
|
|
238
|
-
updateStatus(
|
|
246
|
+
updateStatus(`Sent timer command: ${command}`);
|
|
239
247
|
}
|
|
240
|
-
|
|
241
|
-
setInterval(() => {
|
|
242
|
-
if (timeToStart !== null && timeToStart > 0) {
|
|
243
|
-
timeToStart--;
|
|
244
|
-
updateTimerDisplay();
|
|
245
|
-
}
|
|
246
|
-
}, 1000);
|
|
247
|
-
|
|
248
248
|
</script>
|
|
249
249
|
|
|
250
250
|
<div style="display: flex; justify-content: center; align-items: center; gap: 3em; flex-wrap: wrap;">
|
package/racer-kip.png
ADDED
|
Binary file
|
|
Binary file
|