pypilot-autopilot-provider 1.0.0 → 1.1.0-beta.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/CHANGELOG.md CHANGED
@@ -1,18 +1,26 @@
1
1
  # CHANGELOG: PyPilot-Autopilot-Provider - Signal K plugin
2
2
 
3
+ ## v1.1.0-beta.2
4
+
5
+ Add support for `actions`.
6
+
7
+
3
8
  ## v1.0.0
4
9
 
5
10
  Initial release.
6
11
 
12
+
7
13
  ## v1.0.0-beta.3
8
14
 
9
15
  - Added support for manual steering when pilot is not engaged.
10
16
  - Updated angle processing to use values in degrees from Signal K server interface.
11
17
 
18
+
12
19
  ## v1.0.0-beta.2
13
20
 
14
21
  - Updated to align with [Signal K Server PR #1596](https://github.com/SignalK/signalk-server/pull/1596).
15
22
 
23
+
16
24
  ## v1.0.0-beta.1
17
25
 
18
26
  - Initial beta release.
package/README.md CHANGED
@@ -3,7 +3,26 @@
3
3
 
4
4
  ## About
5
5
 
6
- Signal K server plugin for PyPilot that enables commands to be sent by Signal K client applications via the Autopilot API .
6
+ Signal K server plugin for PyPilot that enables commands to be sent by Signal K client applications via the Autopilot API.
7
+
8
+ This plugin provides support for the `actions` enhancement to the Signal K Autopilot API and makes available the following actions:
9
+
10
+ - tack
11
+ - courseCurrentPoint
12
+
13
+ > **Note:** The actions available for use will change based on the current state of the autopilot.
14
+ > - An error response will be received when trying to invoke an action that is not available.
15
+
16
+ _e.g. **courseCurrentPoint** action is only available when:_
17
+ 1. _`navigation.course.nextPoint` Signal K path contains data._
18
+ 1. _PyPilot `nav` mode is available for selection._
19
+
20
+ ## `engage` Operation Behaviour
21
+
22
+ Submitting an `enage` request via the Autopilot API will result in the following behaviour:
23
+ 1. If the `courseCurrentPoint` action is available, it will be executed _(place Pypilot into `nav` mode, and `enable`)_
24
+ 2. Enable PyPilot in its current `mode` setting.
25
+
7
26
 
8
27
 
9
28
  ## Requirements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pypilot-autopilot-provider",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-beta.2",
4
4
  "description": "PyPilot autopilot provider plugin for Signal K server.",
5
5
  "main": "plugin/index.js",
6
6
  "keywords": [
@@ -29,7 +29,8 @@
29
29
  "socket.io-client": "^4.4.1"
30
30
  },
31
31
  "devDependencies": {
32
- "@signalk/server-api": "^2.3.0",
32
+ "@signalk/server-api": "^2.6.1",
33
+ "@types/baconjs": "^0.7.34",
33
34
  "@types/express": "^4.17.6",
34
35
  "@types/node-fetch": "^2.5.6",
35
36
  "prettier": "^2.5.1",
package/plugin/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ServerAPI } from '@signalk/server-api';
2
- import { AutopilotProviderRegistry } from './sk-api';
1
+ import { ServerAPI, AutopilotProviderRegistry } from '@signalk/server-api';
3
2
  export interface AutopilotProviderApp extends ServerAPI, AutopilotProviderRegistry {
4
3
  }
package/plugin/index.js CHANGED
@@ -125,13 +125,19 @@ module.exports = (server) => {
125
125
  return;
126
126
  }),
127
127
  engage: (deviceId) => __awaiter(void 0, void 0, void 0, function* () {
128
- (0, pypilot_1.apSetState)('enabled');
128
+ (0, pypilot_1.engagePilot)();
129
129
  return;
130
130
  }),
131
131
  disengage: (deviceId) => __awaiter(void 0, void 0, void 0, function* () {
132
132
  (0, pypilot_1.apSetState)('disabled');
133
133
  return;
134
134
  }),
135
+ courseCurrentPoint: (deviceId) => __awaiter(void 0, void 0, void 0, function* () {
136
+ yield (0, pypilot_1.apSetNavMode)();
137
+ }),
138
+ courseNextPoint: (deviceId) => __awaiter(void 0, void 0, void 0, function* () {
139
+ throw new Error('Not implemented!');
140
+ }),
135
141
  tack: (direction, deviceId) => __awaiter(void 0, void 0, void 0, function* () {
136
142
  return (0, pypilot_1.apTack)(direction === 'port' ? true : false);
137
143
  }),
@@ -1,5 +1,5 @@
1
1
  import { AutopilotProviderApp } from './';
2
- import { AutopilotInfo } from './sk-api';
2
+ import { AutopilotInfo } from '@signalk/server-api';
3
3
  export interface PYPILOT_CONFIG {
4
4
  host: string;
5
5
  port: number;
@@ -8,6 +8,8 @@ export declare const apData: AutopilotInfo;
8
8
  export declare const PILOTIDS: string[];
9
9
  export declare const initPyPilot: (app: AutopilotProviderApp, id: string, config: PYPILOT_CONFIG) => void;
10
10
  export declare const closePyPilot: () => void;
11
+ export declare const apSetNavMode: () => Promise<void>;
12
+ export declare const engagePilot: () => Promise<void>;
11
13
  export declare const apSetState: (state: string) => boolean;
12
14
  export declare const apSetMode: (mode: string) => void;
13
15
  export declare const apSetTarget: (value: number) => void;
package/plugin/pypilot.js CHANGED
@@ -1,6 +1,15 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apTack = exports.apDodge = exports.apSetTarget = exports.apSetMode = exports.apSetState = exports.closePyPilot = exports.initPyPilot = exports.PILOTIDS = exports.apData = void 0;
12
+ exports.apTack = exports.apDodge = exports.apSetTarget = exports.apSetMode = exports.apSetState = exports.engagePilot = exports.apSetNavMode = exports.closePyPilot = exports.initPyPilot = exports.PILOTIDS = exports.apData = void 0;
4
13
  const socket_io_client_1 = require("socket.io-client");
5
14
  // autopilot data
6
15
  exports.apData = {
@@ -9,7 +18,24 @@ exports.apData = {
9
18
  { name: 'enabled', engaged: true },
10
19
  { name: 'disabled', engaged: false }
11
20
  ],
12
- modes: []
21
+ modes: [],
22
+ actions: [
23
+ {
24
+ id: 'tack',
25
+ name: 'Tack',
26
+ available: false
27
+ },
28
+ {
29
+ id: 'courseCurrentPoint',
30
+ name: 'To Destination',
31
+ available: false
32
+ },
33
+ {
34
+ id: 'courseNextPoint',
35
+ name: 'Adv. Waypoint',
36
+ available: false
37
+ }
38
+ ]
13
39
  },
14
40
  state: 'disabled',
15
41
  mode: null,
@@ -21,6 +47,7 @@ let pluginId;
21
47
  let socket;
22
48
  const degToRad = (deg) => deg * (Math.PI / 180);
23
49
  const radToDeg = (rad) => rad * (180 / Math.PI);
50
+ let pypilotModes = []; // available modes received from PyPilot
24
51
  exports.PILOTIDS = ['pypilot-sk'];
25
52
  // initialise connection to autopilot and register pypilot_web socket listeners
26
53
  const initPyPilot = (app, id, config) => {
@@ -55,24 +82,18 @@ const initPyPilotListeners = () => {
55
82
  socket.emit('pypilot', `watch={"ap.heading_command": ${JSON.stringify(period)}}`);
56
83
  socket.emit('pypilot', `watch={"ap.enabled": ${JSON.stringify(period)}}`);
57
84
  socket.emit('pypilot', `watch={"ap.mode": ${JSON.stringify(period)}}`);
85
+ socket.emit('pypilot', `watch={"ap.modes": ${JSON.stringify(period)}}`);
86
+ socket.emit('pypilot', `watch={"profiles": ${JSON.stringify(period)}}`);
58
87
  }, 1000);
59
88
  exports.apData.state = 'disabled';
60
- server.autopilotUpdate(exports.PILOTIDS[0], {
61
- state: exports.apData.state,
62
- mode: exports.apData.mode,
63
- target: exports.apData.target,
64
- engaged: exports.apData.engaged
65
- });
89
+ sendToSK();
66
90
  });
67
91
  socket.on('connect_error', () => {
68
92
  server.debug('socket connect_error!');
69
93
  server.setPluginStatus(`Unable to connect to PyPilot!`);
70
94
  exports.apData.state = 'off-line';
71
95
  exports.apData.engaged = false;
72
- server.autopilotUpdate(exports.PILOTIDS[0], {
73
- state: exports.apData.state,
74
- engaged: exports.apData.engaged
75
- });
96
+ sendToSK();
76
97
  });
77
98
  // pypilot updates listener (values)
78
99
  socket.on('pypilot', (msg) => {
@@ -153,6 +174,63 @@ const sendToPyPilot = (path, value) => {
153
174
  throw new Error('Error: Invalid value!');
154
175
  }
155
176
  };
177
+ // send status update to SK Server
178
+ const sendToSK = () => {
179
+ setAvailableActions();
180
+ server.autopilotUpdate(exports.PILOTIDS[0], {
181
+ state: exports.apData.state,
182
+ mode: exports.apData.mode,
183
+ target: exports.apData.target,
184
+ engaged: exports.apData.engaged,
185
+ availableActions: getAvailableActions()
186
+ });
187
+ };
188
+ // return array of availableActions
189
+ const getAvailableActions = () => {
190
+ return exports.apData.options.actions
191
+ .filter((i) => i.available)
192
+ .map((i) => i.id);
193
+ };
194
+ // update the options.actions array
195
+ const setAvailableActions = () => {
196
+ if (exports.apData.engaged) {
197
+ exports.apData.options.actions.forEach((i) => {
198
+ if (i.id === 'tack') {
199
+ i.available = true;
200
+ }
201
+ if (i.id === 'courseCurrentPoint') {
202
+ i.available = pypilotModes.includes('nav');
203
+ }
204
+ });
205
+ }
206
+ else {
207
+ exports.apData.options.actions.forEach((i) => {
208
+ i.available = false;
209
+ });
210
+ }
211
+ };
212
+ // set nav mode if available
213
+ const apSetNavMode = () => __awaiter(void 0, void 0, void 0, function* () {
214
+ const cdata = yield server.getCourse();
215
+ if (cdata.nextPoint && getAvailableActions().includes('courseCurrentPoint')) {
216
+ (0, exports.apSetMode)('nav');
217
+ setTimeout(() => (0, exports.apSetState)('enabled'), 500);
218
+ }
219
+ else {
220
+ throw new Error('Nav mode is not available!');
221
+ }
222
+ });
223
+ exports.apSetNavMode = apSetNavMode;
224
+ // perform `engage` operation
225
+ const engagePilot = () => __awaiter(void 0, void 0, void 0, function* () {
226
+ try {
227
+ yield (0, exports.apSetNavMode)();
228
+ }
229
+ catch (err) {
230
+ (0, exports.apSetState)('enabled');
231
+ }
232
+ });
233
+ exports.engagePilot = engagePilot;
156
234
  // process received pypilot update messages (values)
157
235
  const handlePyPilotUpdateMsg = (data) => {
158
236
  // compare and send delta
@@ -162,45 +240,35 @@ const handlePyPilotUpdateMsg = (data) => {
162
240
  const rad = degToRad(heading);
163
241
  if (rad !== exports.apData.target) {
164
242
  exports.apData.target = rad;
165
- server.autopilotUpdate(exports.PILOTIDS[0], {
166
- target: exports.apData.target
167
- });
168
243
  }
169
244
  }
170
245
  }
171
246
  if (typeof data['ap.mode'] !== 'undefined') {
172
247
  if (data['ap.mode'] !== exports.apData.mode) {
173
248
  exports.apData.mode = data['ap.mode'];
174
- server.autopilotUpdate(exports.PILOTIDS[0], {
175
- mode: exports.apData.mode
176
- });
249
+ if (exports.apData.options.modes.length === 0) {
250
+ exports.apData.options.modes.push(exports.apData.mode);
251
+ }
177
252
  }
178
253
  }
254
+ if (typeof data['ap.modes'] !== 'undefined') {
255
+ pypilotModes = data['ap.modes'];
256
+ }
179
257
  if (typeof data['ap.enabled'] !== 'undefined') {
180
- if (data['ap.enabled'] !== exports.apData.engaged) {
181
- exports.apData.state = data['ap.enabled'] ? 'enabled' : 'disabled';
182
- exports.apData.engaged = data['ap.enabled'];
183
- server.autopilotUpdate(exports.PILOTIDS[0], {
184
- state: exports.apData.state,
185
- engaged: exports.apData.engaged
186
- });
187
- }
258
+ exports.apData.state = data['ap.enabled'] ? 'enabled' : 'disabled';
259
+ exports.apData.engaged = data['ap.enabled'];
188
260
  }
189
261
  if (typeof data['ap.heading'] !== 'undefined') {
190
- server.autopilotUpdate(exports.PILOTIDS[0], {
191
- state: exports.apData.state,
192
- engaged: exports.apData.engaged
193
- });
194
262
  }
263
+ sendToSK();
195
264
  };
196
265
  // process received pypilot_values message (choices)
197
266
  const handlePyPilotValuesMsg = (data) => {
198
- // available modes
199
- if (typeof data['ap.mode'] !== undefined && data['ap.mode'].choices) {
200
- exports.apData.options.modes = Array.isArray(data['ap.mode'].choices)
201
- ? data['ap.mode'].choices
202
- : [];
203
- }
267
+ var _a;
268
+ // supported modes
269
+ exports.apData.options.modes = Array.isArray((_a = data['ap.mode']) === null || _a === void 0 ? void 0 : _a.choices)
270
+ ? data['ap.mode'].choices
271
+ : pypilotModes !== null && pypilotModes !== void 0 ? pypilotModes : [];
204
272
  };
205
273
  // set autopilot state
206
274
  const apSetState = (state) => {
File without changes
package/plugin/sk-api.js CHANGED
File without changes