signalk-tides 1.4.0 → 2.0.0-beta.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.
@@ -0,0 +1,65 @@
1
+ name: CI
2
+
3
+ on:
4
+ push: {}
5
+
6
+ jobs:
7
+ lint:
8
+ name: Lint
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Checkout repository
12
+ uses: actions/checkout@v6
13
+
14
+ - name: Setup Node.js
15
+ uses: actions/setup-node@v6
16
+
17
+ - name: Install dependencies
18
+ run: npm install
19
+
20
+ - name: Run linter
21
+ run: npm run lint
22
+
23
+ test:
24
+ name: Test
25
+ runs-on: ubuntu-latest
26
+ env:
27
+ CI: true
28
+ steps:
29
+ - name: Checkout repository
30
+ uses: actions/checkout@v6
31
+
32
+ - name: Setup Node.js
33
+ uses: actions/setup-node@v6
34
+
35
+ - name: Install dependencies
36
+ run: npm install
37
+
38
+ - name: Install Playwright browsers
39
+ run: npx playwright install chromium
40
+
41
+ - name: Run build
42
+ run: npm run build
43
+
44
+ - name: Run Tests
45
+ run: npm run test
46
+
47
+ publish:
48
+ needs: [lint, test]
49
+ runs-on: ubuntu-latest
50
+ permissions:
51
+ id-token: write
52
+ pull-requests: write
53
+ steps:
54
+ - uses: actions/checkout@v6
55
+
56
+ - uses: actions/setup-node@v6
57
+
58
+ - name: Install modules
59
+ run: npm install
60
+
61
+ - name: Build
62
+ run: npm run build
63
+
64
+ - name: Publish to pkg.pr.new
65
+ run: npx pkg-pr-new publish
@@ -0,0 +1 @@
1
+ [ 2058ms] [ERROR] Failed to load resource: net::ERR_NETWORK_IO_SUSPENDED @ https://docs.stormglass.io/_media/logo.svg:0
@@ -0,0 +1 @@
1
+ [ 239ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/signalk-tides/assets/icon.svg:0
@@ -0,0 +1,44 @@
1
+ [ 260ms] [LOG] [ConnectionStateMachine] Service initialized @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
2
+ [ 265ms] [LOG] [AppInit Network Service] Connection Configuration not found. Creating configuration using Auto-Discovery URL: http://localhost:3000 @ http://localhost:3000/@mxtommy/kip/main-UBENKC2D.js:0
3
+ [ 268ms] [LOG] [ConnectionStateMachine] Starting HTTP discovery: Connecting to http://localhost:3000 @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
4
+ [ 268ms] [LOG] [Connection Service] Connecting to: http://localhost:3000 @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
5
+ [ 279ms] [LOG] [ConnectionStateMachine] HTTP discovery successful @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
6
+ [ 279ms] [LOG] [AppSettings Service] Service startup... @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:3
7
+ [ 279ms] [LOG] [AppSettings Service] LocalStorage enabled @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:4
8
+ [ 279ms] [LOG] [AppSettings Service] Error loading appConfig config. Force loading appConfig defaults @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:4
9
+ [ 279ms] [LOG] [AppSettings Service] Error loading dashboardsConfig config. Force loading dashboardsConfig defaults @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:4
10
+ [ 279ms] [LOG] [AppSettings Service] Error loading themeConfig config. Force loading themeConfig defaults @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:4
11
+ [ 280ms] [LOG] [AppSettings Service] Saving Application config to LocalStorage @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:4
12
+ [ 280ms] [LOG] [DatasetStreamService] Auto Starting 0 Datasets @ http://localhost:3000/@mxtommy/kip/chunk-5SMHTIVS.js:0
13
+ [ 280ms] [LOG] [AppInit Network Service] Initialization completed @ http://localhost:3000/@mxtommy/kip/main-UBENKC2D.js:0
14
+ [ 280ms] [LOG] [ConnectionStateMachine] WebSocket mode enabled @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
15
+ [ 280ms] [LOG] [AppInit Network Service] Starting WebSocket connection after initialization @ http://localhost:3000/@mxtommy/kip/main-UBENKC2D.js:0
16
+ [ 280ms] [LOG] [ConnectionStateMachine] Starting WebSocket connection @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
17
+ [ 280ms] [LOG] [Delta Service] ConnectionStateMachine request: WebSocket opening... @ http://localhost:3000/@mxtommy/kip/chunk-IXUKD73N.js:0
18
+ [ 287ms] [WARNING] [Dashboard Service] No dashboards found in settings, creating blank dashboard @ http://localhost:3000/@mxtommy/kip/chunk-5SMHTIVS.js:0
19
+ [ 289ms] [LOG] [Remote Dashboards] Cleaning paths on server @ http://localhost:3000/@mxtommy/kip/main-UBENKC2D.js:0
20
+ [ 290ms] [LOG] [UI Event Service] PWA mode: false @ http://localhost:3000/@mxtommy/kip/chunk-5SMHTIVS.js:49
21
+ [ 300ms] [LOG] *********** KIP Version Information *********** @ http://localhost:3000/@mxtommy/kip/chunk-NS2OV2OW.js:0
22
+ [ 300ms] [LOG] ** App Version: 4.8.0 @ http://localhost:3000/@mxtommy/kip/chunk-NS2OV2OW.js:0
23
+ [ 300ms] [LOG] ** Browser Version: Chrome 149 @ http://localhost:3000/@mxtommy/kip/chunk-NS2OV2OW.js:0
24
+ [ 300ms] [LOG] ** OS Version: macOS @ http://localhost:3000/@mxtommy/kip/chunk-NS2OV2OW.js:0
25
+ [ 300ms] [LOG] *********************************************** @ http://localhost:3000/@mxtommy/kip/chunk-NS2OV2OW.js:0
26
+ [ 305ms] [LOG] [AppSettings Service] Saving Dashboard config to LocalStorage @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:4
27
+ [ 323ms] [LOG] [Delta Service] WebSocket connected without Authorization Token @ http://localhost:3000/@mxtommy/kip/chunk-IXUKD73N.js:0
28
+ [ 324ms] [LOG] [ConnectionStateMachine] WebSocket connected @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
29
+ [ 324ms] [LOG] [Connection Service] Server Name: signalk-server, Version: 2.23.0-beta.1, Roles: ["master","main"] @ http://localhost:3000/@mxtommy/kip/chunk-2TP7C66X.js:0
30
+ [ 437ms] [WARNING] [Request Service] Status Code: 403 - DENIED: Authorization with R/W or Admin permission level is required to send commands. Configure Sign In credential. @ http://localhost:3000/@mxtommy/kip/chunk-W6MCE3GH.js:0
31
+ [ 437ms] [WARNING] [Request Service] Status Code: 403 - DENIED: Authorization with R/W or Admin permission level is required to send commands. Configure Sign In credential. @ http://localhost:3000/@mxtommy/kip/chunk-W6MCE3GH.js:0
32
+ [ 437ms] [WARNING] [Request Service] Status Code: 403 - DENIED: Authorization with R/W or Admin permission level is required to send commands. Configure Sign In credential. @ http://localhost:3000/@mxtommy/kip/chunk-W6MCE3GH.js:0
33
+ [ 450ms] [WARNING] An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing. @ http://localhost:3000/@mxtommy/kip/chunk-LEY6MANN.js:3
34
+ [ 515ms] [WARNING] An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing. @ http://localhost:3000/@signalk/freeboard-sk/:0
35
+ [ 1311ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:3000/plugins/freeboard-sk:0
36
+ [ 1387ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:3000/plugins/kip:0
37
+ [ 1387ms] [LOG] [KipSeriesApiClientService] POST http://localhost:3000/plugins/kip/series/reconcile (0 series) @ http://localhost:3000/@mxtommy/kip/chunk-6ZR3M3IQ.js:2
38
+ [ 1403ms] [INFO] Internet connection detected. @ http://localhost:3000/@signalk/freeboard-sk/main-YHMZXR5G.js:249
39
+ [ 1414ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:3000/plugins/kip/series/reconcile:0
40
+ [ 1414ms] [ERROR] [KipSeriesApiClientService] Series reconcile request failed: Ar @ http://localhost:3000/@mxtommy/kip/chunk-6ZR3M3IQ.js:2
41
+ [ 1517ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/signalk/v1/api/vessels/self/navigation/anchor:0
42
+ [ 1784ms] [WARNING] Blocked aria-hidden on an element because its descendant retained focus. The focus must not be hidden from assistive technology users. Avoid using aria-hidden on a focused element or its ancestor. Consider using the inert attribute instead, which will also prevent focus. For more details, see the aria-hidden section of the WAI-ARIA specification at https://w3c.github.io/aria/#aria-hidden.
43
+ Element with focus: <div>
44
+ Ancestor with aria-hidden: <app-root> @ http://localhost:3000/@signalk/freeboard-sk/:0
@@ -0,0 +1 @@
1
+ [ 68ms] [LOG] [Delta Service] App suspended @ http://localhost:3000/@mxtommy/kip/chunk-IXUKD73N.js:0
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # signalk-tides
2
2
 
3
- A SignalK plugin that provides tidal predictions for the vessel's position from various online sources.
3
+ A SignalK plugin that provides offline tidal predictions for the vessel's position, powered by [Neaps](https://github.com/neaps/neaps).
4
+
5
+ Since 2.0, predictions are computed locally from harmonic constituents — no network access or API keys required.
4
6
 
5
7
  ## Installation
6
8
 
7
- 1. Install `signalk-tides` from the SignalK Appstore or manually by running `npm install signalk-tides` in the SignalK server directory (`~/.signalk`).
9
+ Install `signalk-tides` from the SignalK Appstore or manually by running `npm install signalk-tides` in the SignalK server directory (`~/.signalk`).
8
10
 
9
- 2. Optionally go to the plugin settings in "Server => Plugin Config => Tides" and configure which source to use.
10
11
 
11
12
  ## Usage
12
13
 
@@ -21,195 +22,41 @@ It publishes the following [tide data](https://signalk.org/specification/1.7.0/d
21
22
  * `environment.tide.heightNow`
22
23
  * `environment.tide.stationName`
23
24
 
24
- ### Tides resource
25
+ ### Tides API
25
26
 
26
- It also registers a `tides` resource, which returns tide predictions for yesterday and the next 6 days for the vessel's position.
27
+ The plugin mounts the [Neaps API](https://github.com/neaps/neaps) at `/signalk/v2/api/tides`, which serves station search, extremes, and timeline predictions. The synthetic station `vessel/current` resolves to the nearest station to the vessel:
27
28
 
28
29
  ```
29
- $ curl http://localhost:3000/signalk/v2/api/resources/tides
30
+ $ curl http://localhost:3000/signalk/v2/api/tides/stations/vessel/current/extremes
30
31
  ```
31
32
 
32
- ##### Request Parameters
33
+ ### Tides resource
34
+
35
+ It also registers a `tides` resource, which returns the next 7 days of tide extremes for the vessel's position.
33
36
 
34
- * `date` (optional) - the date for which to get the tide predictions in the format `YYYY-MM-DD`. Default: yesterday's date.
37
+ ```
38
+ $ curl http://localhost:3000/signalk/v2/api/resources/tides
39
+ ```
35
40
 
36
41
  ##### Response
37
42
 
38
43
  ```json
39
44
  {
40
- "extremes" : [
41
- {
42
- "time" : "2025-03-29T00:45:00.000Z",
43
- "type" : "Low",
44
- "value" : 0.025
45
- },
46
- {
47
- "time" : "2025-03-29T07:20:00.000Z",
48
- "type" : "High",
49
- "value" : 1.928
50
- },
51
- {
52
- "time" : "2025-03-29T13:18:00.000Z",
53
- "type" : "Low",
54
- "value" : 0.044
55
- },
56
- {
57
- "time" : "2025-03-29T19:45:00.000Z",
58
- "type" : "High",
59
- "value" : 1.815
60
- },
61
- {
62
- "time" : "2025-03-30T01:24:00.000Z",
63
- "type" : "Low",
64
- "value" : 0.164
65
- },
66
- {
67
- "time" : "2025-03-30T07:52:00.000Z",
68
- "type" : "High",
69
- "value" : 2.024
70
- },
71
- {
72
- "time" : "2025-03-30T14:05:00.000Z",
73
- "type" : "Low",
74
- "value" : -0.131
75
- },
76
- {
77
- "time" : "2025-03-30T20:43:00.000Z",
78
- "type" : "High",
79
- "value" : 1.733
80
- },
81
- {
82
- "time" : "2025-03-31T02:05:00.000Z",
83
- "type" : "Low",
84
- "value" : 0.334
85
- },
86
- {
87
- "time" : "2025-03-31T08:27:00.000Z",
88
- "type" : "High",
89
- "value" : 2.084
90
- },
91
- {
92
- "time" : "2025-03-31T14:53:00.000Z",
93
- "type" : "Low",
94
- "value" : -0.247
95
- },
96
- {
97
- "time" : "2025-03-31T21:44:00.000Z",
98
- "type" : "High",
99
- "value" : 1.632
100
- },
101
- {
102
- "time" : "2025-04-01T02:48:00.000Z",
103
- "type" : "Low",
104
- "value" : 0.517
105
- },
106
- {
107
- "time" : "2025-04-01T09:06:00.000Z",
108
- "type" : "High",
109
- "value" : 2.094
110
- },
111
- {
112
- "time" : "2025-04-01T15:46:00.000Z",
113
- "type" : "Low",
114
- "value" : -0.29
115
- },
116
- {
117
- "time" : "2025-04-01T22:51:00.000Z",
118
- "type" : "High",
119
- "value" : 1.529
120
- },
121
- {
122
- "time" : "2025-04-02T03:37:00.000Z",
123
- "type" : "Low",
124
- "value" : 0.695
125
- },
126
- {
127
- "time" : "2025-04-02T09:49:00.000Z",
128
- "type" : "High",
129
- "value" : 2.05
130
- },
131
- {
132
- "time" : "2025-04-02T16:43:00.000Z",
133
- "type" : "Low",
134
- "value" : -0.263
135
- },
136
- {
137
- "time" : "2025-04-03T00:04:00.000Z",
138
- "type" : "High",
139
- "value" : 1.448
140
- },
141
- {
142
- "time" : "2025-04-03T04:35:00.000Z",
143
- "type" : "Low",
144
- "value" : 0.847
145
- },
146
- {
147
- "time" : "2025-04-03T10:39:00.000Z",
148
- "type" : "High",
149
- "value" : 1.957
150
- },
151
- {
152
- "time" : "2025-04-03T17:50:00.000Z",
153
- "type" : "Low",
154
- "value" : -0.189
155
- },
156
- {
157
- "time" : "2025-04-04T01:24:00.000Z",
158
- "type" : "High",
159
- "value" : 1.421
160
- },
161
- {
162
- "time" : "2025-04-04T05:49:00.000Z",
163
- "type" : "Low",
164
- "value" : 0.946
165
- },
166
- {
167
- "time" : "2025-04-04T11:40:00.000Z",
168
- "type" : "High",
169
- "value" : 1.837
170
- },
171
- {
172
- "time" : "2025-04-04T19:07:00.000Z",
173
- "type" : "Low",
174
- "value" : -0.11
175
- },
176
- {
177
- "time" : "2025-04-05T02:40:00.000Z",
178
- "type" : "High",
179
- "value" : 1.455
180
- },
181
- {
182
- "time" : "2025-04-05T07:19:00.000Z",
183
- "type" : "Low",
184
- "value" : 0.958
185
- },
186
- {
187
- "time" : "2025-04-05T12:53:00.000Z",
188
- "type" : "High",
189
- "value" : 1.728
190
- },
191
- {
192
- "time" : "2025-04-05T20:25:00.000Z",
193
- "type" : "Low",
194
- "value" : -0.061
195
- }
196
- ],
197
- "station" : {
198
- "name" : "Rincon Point, Pier 22 1/2",
199
- "position" : {
200
- "latitude" : 37.79,
201
- "longitude" : -122.387
202
- }
203
- }
45
+ "datum": "MLLW",
46
+ "units": "meters",
47
+ "station": {
48
+ "id": "noaa/9414290",
49
+ "name": "San Francisco",
50
+ "latitude": 37.806,
51
+ "longitude": -122.465
52
+ },
53
+ "extremes": [
54
+ { "time": "2025-03-29T00:45:00.000Z", "level": 0.025, "high": false, "low": true, "label": "Low" },
55
+ { "time": "2025-03-29T07:20:00.000Z", "level": 1.928, "high": true, "low": false, "label": "High" }
56
+ ]
204
57
  }
205
58
  ```
206
59
 
207
- ## Sources
208
-
209
- - [NOAA](https://tidesandcurrents.noaa.gov/web_services_info.html) (US only)
210
- - [WorldTides API](https://www.worldtides.info/) (requires an API key)
211
- - [StormGlass.io](https://stormglass.io/) (requires an API key)
212
-
213
60
  ## License
214
61
 
215
62
  This plugin is a fork of the [signalk-tides-api](https://github.com/joabakk/signalk-tides-api) plugin (which is no longer working) and is licensed under the [Apache License 2.0](LICENSE). Kudos to @joabakk and @sbender9 for the original work.
package/dist/cache.js CHANGED
@@ -1,28 +1,25 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const path_1 = require("path");
4
- const promises_1 = require("fs/promises");
5
- class FileCache {
1
+ import { join } from "path";
2
+ import { writeFile, readFile, mkdir } from "fs/promises";
3
+ export default class FileCache {
6
4
  path;
7
5
  constructor(path) {
8
6
  this.path = path;
9
7
  }
10
8
  async get(key) {
11
9
  try {
12
- const content = await (0, promises_1.readFile)(this.getKey(key), 'utf-8');
10
+ const content = await readFile(this.getKey(key), "utf-8");
13
11
  return JSON.parse(content);
14
12
  }
15
- catch (err) {
13
+ catch {
16
14
  return undefined;
17
15
  }
18
16
  }
19
17
  async set(key, value) {
20
18
  // Ensure directory exists
21
- await (0, promises_1.mkdir)(this.path, { recursive: true });
22
- return (0, promises_1.writeFile)(this.getKey(key), JSON.stringify(value));
19
+ await mkdir(this.path, { recursive: true });
20
+ return writeFile(this.getKey(key), JSON.stringify(value));
23
21
  }
24
22
  getKey(key) {
25
- return (0, path_1.join)(this.path, `${key}.json`);
23
+ return join(this.path, `${key}.json`);
26
24
  }
27
25
  }
28
- exports.default = FileCache;
@@ -1,18 +1,15 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.approximateTideHeightAt = approximateTideHeightAt;
4
1
  /** Given a list of tide extremes, estimate the height at a specific time. */
5
- function approximateTideHeightAt(extremes, time) {
6
- const sorted = extremes.slice().sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
7
- const prev = sorted.filter(h => new Date(h.time) <= time).at(-1);
8
- const next = sorted.filter(h => new Date(h.time) >= time).at(0);
2
+ export function approximateTideHeightAt(extremes, time) {
3
+ const sorted = extremes.slice().sort((a, b) => a.time.getTime() - b.time.getTime());
4
+ const prev = sorted.filter(h => h.time <= time).at(-1);
5
+ const next = sorted.filter(h => h.time >= time).at(0);
9
6
  if (!prev)
10
7
  throw new Error("Missing height data before " + time.toISOString());
11
8
  if (!next)
12
9
  throw new Error("Missing height data after " + time.toISOString());
13
- const progress = (time.getTime() - new Date(prev.time).getTime()) /
14
- (new Date(next.time).getTime() - new Date(prev.time).getTime());
15
- const value = interpolate(prev.value, next.value, easeSine(progress));
10
+ const progress = (time.getTime() - prev.time.getTime()) /
11
+ (next.time.getTime() - prev.time.getTime());
12
+ const value = interpolate(prev.level, next.level, easeSine(progress));
16
13
  return parseFloat(value.toFixed(3));
17
14
  }
18
15
  /** Interpolate between two values using the given progress (0-1). */