signalk-racer 0.9.0 → 0.9.2

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 CHANGED
@@ -10,17 +10,17 @@ GitHub repository: [https://github.com/gregw/signalk-racer](https://github.com/g
10
10
 
11
11
  This plugin calculates and publishes the following Signal K paths:
12
12
 
13
- | Path | Description | Units | Standard |
14
- |---------------------------------------------|--------------------------------------------------------|---------|----------|
15
- | `navigation.racing.distanceStartline` | Signed minimum distance from the bow to the start line | `m` | Y |
16
- | `navigation.racing.startLineLength` | Total length of the start line | `m` | |
17
- | `navigation.racing.stbLineBias` | Bias of the start line toward the starboard end | `m` | |
18
- | `navigation.racing.startLinePort.latitude` | Latitude of the port (pin) end of the start line | `deg` | Y |
19
- | `navigation.racing.startLinePort.longitude` | Longitude of the port end | `deg` | Y |
20
- | `navigation.racing.startLineStb.latitude` | Latitude of the starboard (boat) end of the start line | `deg` | Y |
21
- | `navigation.racing.startLineStb.longitude` | Longitude of the starboard end | `deg` | Y |
22
- | `navigation.racing.nextLegHeading` | True heading for the next leg of the course | `deg` | |
23
- | `navigation.racing.nextLegTWA` | True Wind Angle for the next leg of the course | `deg` | |
13
+ | Path | Description | Units | Schema |
14
+ |---------------------------------------------|---------------------------------------------------------|---------|--------|
15
+ | `navigation.racing.`<br/>`distanceStartline` | Signed minimum distance from the bow to the start line | `m` | Y |
16
+ | `navigation.racing.`<br/>`startLineLength` | Total length of the start line | `m` | |
17
+ | `navigation.racing.`<br/>`stbLineBias` | Bias of the start line toward the starboard end | `m` | |
18
+ | `navigation.racing.`<br/>`startLinePort.latitude` | Latitude of the port (pin) end of the start line | `deg` | Y |
19
+ | `navigation.racing.`<br/>`startLinePort.longitude` | Longitude of the port (pin) end of the start line | `deg` | Y |
20
+ | `navigation.racing.`<br/>`startLineStb.latitude` | Latitude of the starboard (boat) end of the start line | `deg` | Y |
21
+ | `navigation.racing.`<br/>`startLineStb.longitude` | Longitude of the starboard (boat) end of the start line | `deg` | Y |
22
+ | `navigation.racing.`<br/>`nextLegHeading` | True heading for the next leg of the course | `deg` | |
23
+ | `navigation.racing.`<br/>`nextLegTWA` | True Wind Angle for the next leg of the course | `deg` | |
24
24
 
25
25
  These values can be displayed in KIP, Freeboard-SK, or other Signal K clients.
26
26
 
@@ -28,6 +28,7 @@ These values can be displayed in KIP, Freeboard-SK, or other Signal K clients.
28
28
 
29
29
  ---
30
30
 
31
+
31
32
  ## ⚙️ Configuration
32
33
 
33
34
  You can configure the plugin via the Signal K web interface or by editing `settings.json` manually.
@@ -44,16 +45,15 @@ You can configure the plugin via the Signal K web interface or by editing `setti
44
45
 
45
46
  ## 🧠 Algorithm Descriptions
46
47
 
47
- ### 🔹 Waypoint selection (`navigation.racing.startLinePort` and `navigation.racing.startLineStb`)
48
+ ### Waypoint selection (`navigation.racing.startLinePort` and `navigation.racing.startLineStb`)
48
49
 
49
- The `startLineStb` and `startLinePort` configurations are used to search resources for matching waypoints.
50
- These waypoints can be modified in real time (e.g. moving or setting in Freeboard SK) and the algorithm with update to the new line.
50
+ The `startLineStb` and `startLinePort` configurations are used to search resources for matching waypoints. These waypoints can be modified in real time (e.g. moving or setting in Freeboard SK) and the algorithm with update to the new line.
51
51
 
52
- ### 🔹 Bow position
52
+ ### Bow position
53
53
 
54
54
  If `navigation.headingTrue` is not available, then `navigation.courseOverGroundTrue` is used when determining the position of the bow relative to the GPS position.
55
55
 
56
- ### 🔹 Distance to Start Line (`navigation.racing.distanceStartline`)
56
+ ### Distance to Start Line (`navigation.racing.distanceStartline`)
57
57
 
58
58
  - Computes the **signed minimum distance** from the **bow** of the boat to the configured start line.
59
59
  - Uses `sensors.gps.fromBow` and `sensors.gps.fromCenter` offsets to calculate the actual bow position.
@@ -63,26 +63,42 @@ If `navigation.headingTrue` is not available, then `navigation.courseOverGroundT
63
63
  - **Negative** when the bow is **over the line (OCS)**
64
64
  - **Positive** when the bow is **behind the line**
65
65
 
66
- ### 🔹 Start Line Bias (`navigation.racing.stbLineBias`)
66
+ ### Start Line Bias (`navigation.racing.stbLineBias`)
67
67
 
68
68
  - Measures how far ahead (downwind) the **pin** end is relative to the **boat** end.
69
69
  - The `environment.wind.directionTrue` is used as well as the calculated line length and bearing.
70
70
  - A **positive value** means the **pin end is more downwind**, implying the **starboard (boat) end is favored** for upwind starts.
71
71
 
72
- ### 🔹 Line Coordinates & Length
72
+ ### Line Coordinates & Length
73
73
 
74
74
  - The plugin reads Signal K `waypoints` resources for the names you configure.
75
75
  - If both endpoints are found, it calculates:
76
76
  - Line length (using `geolib.getPreciseDistance`)
77
77
  - Line bearing (rhumb line from starboard to port)
78
78
 
79
- ###🔹Next Leg
79
+ ### Next Leg
80
80
  - If a course is active then the true bearing for the next leg is calculated as `navigation.racing.nextLegHeading`
81
81
  - If the `environment.wind.directionTrue` is available, then the True Wind Angle for the next leg is also calculated as `navigation.racing.nextLegHeading`
82
82
  - If the next mark of the active route is beyond the first mark, then the start line calculations are suspended.
83
83
 
84
84
  ---
85
85
 
86
+ ## 🌐 Start Line REST API
87
+
88
+ The plugin provides a REST API that allows external clients (e.g. KIP dashboards, mobile apps, or scripts) to **set or query the start line ends**. This can be used to "ping" the boat or pin end by calling a simple HTTP POST with or without a position.
89
+
90
+ The plugin supports **partial line configuration**: you can set just one end (port or starboard), and it will store and emit that end's position immediately. Once both ends are defined, the full line geometry (length, bearing, bias) will be calculated.
91
+
92
+ ### 📮 API Endpoints
93
+
94
+ | Method | Endpoint | Description |
95
+ |--------|------------------------------------------|------------------------------------------------------------------------------------------------------|
96
+ | `GET` | `/plugins/signalk-racer/startline/port` | Get the last known **port (pin)** end position |
97
+ | `PUT` | `/plugins/signalk-racer/startline/port` | Set the **port (pin)** end of the start line to the current position of the boat plus the bow offset |
98
+ | `GET` | `/plugins/signalk-racer/startline/stb` | Get the last known **starboard (boat)** end position |
99
+ | `PUT` | `/plugins/signalk-racer/startline/stb` | Set the **starboard (boat)** end of the line to the current position of the boat plus the bow offset |
100
+
101
+
86
102
  ## 🔄 Dependencies
87
103
 
88
104
  - [geolib](https://github.com/manuelbieh/geolib): Used for all geospatial math, including distances, bearings, and projections.
@@ -0,0 +1,7 @@
1
+ ## 0.9.0 Initial Release
2
+ + working DTL, Line Length, next TWA
3
+ ## 0.9.1 Format Release
4
+ + formating for the README.TXT
5
+ ## 0.9.2 Set Start Line
6
+ + Added API to set the start line
7
+ + Added webapp to call start line API
package/index.js CHANGED
@@ -178,6 +178,85 @@ module.exports = (app) => {
178
178
  return bow;
179
179
  }
180
180
 
181
+ plugin.setStartLineEnd = async (req, res) => {
182
+ try {
183
+ const end = req.params.end; // "port" or "stb"
184
+ if (end !== 'port' && end !== 'stb') {
185
+ res.status(400).send({error: 'End must be "port" or "stb"'});
186
+ return;
187
+ }
188
+
189
+ let position = app.getSelfPath('navigation.position');
190
+ if (position)
191
+ position = position.value;
192
+ else {
193
+ res.status(500).send({error: 'No valid position available'});
194
+ return;
195
+ }
196
+ const bow = bowPosition(position);
197
+ const startLine = state.startLine;
198
+ const waypointName = options[`startLine${end.charAt(0).toUpperCase() + end.slice(1)}`];
199
+
200
+ let waypointId = startLine ? startLine[end + 'Id'] : null;
201
+ if (!waypointId) {
202
+ // Get the ID of the last existing waypoint with the given name.
203
+ const waypoints = await app.resourcesApi.listResources('waypoints', {});
204
+ for (const [id, resource] of Object.entries(waypoints)) {
205
+ if (resource.name === waypointName) {
206
+ waypointId = id;
207
+ // don't break here as we want the last waypoint with the given name
208
+ }
209
+ }
210
+ }
211
+
212
+ app.debug(`POSITION to set ${end}(${waypointName}/${waypointId}): ${JSON.stringify(bow)}`);
213
+
214
+ const waypoint = {
215
+ name: waypointName,
216
+ feature: {
217
+ type: 'Feature',
218
+ geometry: {
219
+ type: 'Point',
220
+ coordinates: [bow.longitude, bow.latitude]
221
+ },
222
+ properties: {}
223
+ }
224
+ }
225
+
226
+ app.debug(`waypoint: ${waypointId} -> ${end}/${waypointName} : ${JSON.stringify(waypoint)}`);
227
+
228
+ if (waypointId)
229
+ await app.resourcesApi.setResource('waypoints', waypointId, waypoint);
230
+ else
231
+ await app.resourcesApi.createResource('waypoints', waypoint);
232
+
233
+ res.json({
234
+ message: 'OK'
235
+ });
236
+
237
+ if (startLine)
238
+ {
239
+ const newStartLine = { ...state.startLine };
240
+ newStartLine[end + 'Id'] = waypointId;
241
+ newStartLine[end] = bow;
242
+ startLine.length = geolib.getPreciseDistance(newStartLine.port, newStartLine.stb, 0.1);
243
+ startLine.bearing = geolib.getRhumbLineBearing(newStartLine.stb, newStartLine.port);
244
+ state.startLine = newStartLine;
245
+ sendDeltas([
246
+ { path: `navigation.racing.startLine${end.charAt(0).toUpperCase() + end.slice(1)}`, value: bow },
247
+ { path: 'navigation.racing.startLineLength', value: newStartLine.length },
248
+ ]);
249
+
250
+ processPosition(position);
251
+ } else {
252
+ findLineAndThenProcess(position, true);
253
+ }
254
+ } catch (err) {
255
+ app.error('Failed to set start line end:', err);
256
+ res.status(500).json({ error: 'Failed to set start line end' });
257
+ }
258
+ }
259
+
181
260
  function findLineAndThenProcess(position, alwaysFindLine = false) {
182
261
  if (!state.startLine || alwaysFindLine) {
183
262
  app.resourcesApi.listResources(
@@ -196,15 +275,20 @@ module.exports = (app) => {
196
275
  app.debug(`WAYPOINT: ${key}, Value:`, JSON.stringify(value));
197
276
  if (value.name === options.startLinePort) {
198
277
  let pos = waypointToPosition(value);
199
- if (pos)
278
+ if (pos) {
279
+ startLine.portId = key;
200
280
  startLine.port = pos;
281
+ }
201
282
  }
202
283
  if (value.name === options.startLineStb) {
203
284
  let pos = waypointToPosition(value);
204
- if (pos)
285
+ if (pos) {
286
+ startLine.stbId = key;
205
287
  startLine.stb = pos;
288
+ }
206
289
  }
207
290
  }
291
+
208
292
  if (startLine.port && startLine.stb) {
209
293
  startLine.length = geolib.getPreciseDistance(startLine.port, startLine.stb, 0.1);
210
294
  startLine.bearing = geolib.getRhumbLineBearing(startLine.stb, startLine.port);
@@ -494,7 +578,15 @@ module.exports = (app) => {
494
578
  unsubscribes.length = 0;
495
579
  },
496
580
 
497
- schema: () => racerSchema
581
+ schema: () => racerSchema,
582
+
583
+ registerWithRouter: (router) => {
584
+ router.get('/startline/port', (req, res) => res.json(state.startLine ? state.startLine.port : null));
585
+ router.get('/startline/stb', (req, res) => res.json(state.startLine ? state.startLine.stb : null));
586
+ router.put('/startline/:end', plugin.setStartLineEnd);
587
+ },
588
+
589
+ getOpenApi: () => require('./openapi.json'),
498
590
  };
499
591
 
500
592
  return plugin;
package/openapi.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "version": "0.0.1",
5
+ "title": "Signal K Demo/Test Plugin OpenApi",
6
+ "license": {
7
+ "name": "Apache 2.0",
8
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
9
+ }
10
+ },
11
+
12
+ "paths": {
13
+ "/startline/port": {
14
+ "get": {
15
+ "summary": "Set the port end of the start line",
16
+ "responses": {
17
+ "200": { "description": "Port end set" }
18
+ }
19
+ }
20
+ },
21
+ "/startline/stb": {
22
+ "put": {
23
+ "summary": "Set the starboard end of the start line",
24
+ "description": "Set the starboard end of the start line to the boats position plus the bow offset.",
25
+ "responses": {
26
+ "200": { "description": "Starboard end set" }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-racer",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Signalk plugin to calculate values of interest to sail racers: such as Distance to Line; Next leg TWA.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "keywords": [
14
14
  "signalk-node-server-plugin",
15
- "signalk-category-utility"
15
+ "signalk-category-utility",
16
+ "signalk-webapp"
16
17
  ],
17
18
  "signalk-plugin-enabled-by-default": false,
18
19
  "signalk": {
@@ -0,0 +1,52 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>SignalK Racer</title>
6
+ <style>
7
+ body {
8
+ font-family: sans-serif;
9
+ max-width: 600px;
10
+ margin: 3em auto;
11
+ text-align: center;
12
+ }
13
+ button {
14
+ padding: 1em 2em;
15
+ margin: 1em;
16
+ font-size: 1.2em;
17
+ cursor: pointer;
18
+ }
19
+ #status {
20
+ margin-top: 2em;
21
+ font-size: 1em;
22
+ color: green;
23
+ }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <h1>Set Start Line Ends</h1>
28
+ <button onclick="setEnd('port')">Set Port End (Pin)</button>
29
+ <button onclick="setEnd('stb')">Set Starboard End (Boat)</button>
30
+ <div id="status"></div>
31
+
32
+ <script>
33
+ async function setEnd(end) {
34
+ const url = `/plugins/signalk-racer/startline/${end}`;
35
+ document.getElementById('status').textContent = `Sending request to ${url}...`;
36
+
37
+ try {
38
+ const res = await fetch(url, { method: 'PUT' });
39
+ if (res.ok) {
40
+ const data = await res.json();
41
+ document.getElementById('status').textContent = `✅ ${end.toUpperCase()} set at bow position.`;
42
+ } else {
43
+ const err = await res.text();
44
+ throw new Error(`Error ${res.status}: ${err}`);
45
+ }
46
+ } catch (e) {
47
+ document.getElementById('status').textContent = `❌ Failed: ${e.message}`;
48
+ }
49
+ }
50
+ </script>
51
+ </body>
52
+ </html>