@canboat/canboatjs 3.0.2 → 3.2.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.
Files changed (222) hide show
  1. package/.github/workflows/publish.yml +18 -9
  2. package/.github/workflows/test.yml +2 -2
  3. package/.github/workflows/test_canboat_changes.yml +91 -0
  4. package/.husky/pre-commit +4 -0
  5. package/.mocharc.js +7 -0
  6. package/.prettierrc.json +5 -0
  7. package/README.md +5 -14
  8. package/dist/actisense-serial.d.ts +17 -0
  9. package/dist/actisense-serial.d.ts.map +1 -0
  10. package/dist/actisense-serial.js +569 -0
  11. package/dist/actisense-serial.js.map +1 -0
  12. package/dist/bin/actisense-file.d.ts +3 -0
  13. package/dist/bin/actisense-file.d.ts.map +1 -0
  14. package/dist/bin/actisense-file.js +49 -0
  15. package/dist/bin/actisense-file.js.map +1 -0
  16. package/dist/bin/actisense-n2k-tcp.d.ts +3 -0
  17. package/dist/bin/actisense-n2k-tcp.d.ts.map +1 -0
  18. package/dist/bin/actisense-n2k-tcp.js +46 -0
  19. package/dist/bin/actisense-n2k-tcp.js.map +1 -0
  20. package/dist/bin/actisense-serialjs.d.ts +3 -0
  21. package/dist/bin/actisense-serialjs.d.ts.map +1 -0
  22. package/dist/bin/actisense-serialjs.js +51 -0
  23. package/dist/bin/actisense-serialjs.js.map +1 -0
  24. package/dist/bin/analyzerjs.d.ts +3 -0
  25. package/dist/bin/analyzerjs.d.ts.map +1 -0
  26. package/dist/bin/analyzerjs.js +61 -0
  27. package/dist/bin/analyzerjs.js.map +1 -0
  28. package/dist/bin/candumpjs.d.ts +3 -0
  29. package/dist/bin/candumpjs.d.ts.map +1 -0
  30. package/dist/bin/candumpjs.js +73 -0
  31. package/dist/bin/candumpjs.js.map +1 -0
  32. package/dist/bin/cansend.d.ts +3 -0
  33. package/dist/bin/cansend.d.ts.map +1 -0
  34. package/dist/bin/cansend.js +123 -0
  35. package/dist/bin/cansend.js.map +1 -0
  36. package/dist/bin/ikonvert-serial.d.ts +3 -0
  37. package/dist/bin/ikonvert-serial.d.ts.map +1 -0
  38. package/dist/bin/ikonvert-serial.js +36 -0
  39. package/dist/bin/ikonvert-serial.js.map +1 -0
  40. package/dist/bin/to-pgn.d.ts +3 -0
  41. package/dist/bin/to-pgn.d.ts.map +1 -0
  42. package/dist/bin/to-pgn.js +57 -0
  43. package/dist/bin/to-pgn.js.map +1 -0
  44. package/dist/canId.d.ts +21 -0
  45. package/dist/canId.d.ts.map +1 -0
  46. package/dist/canId.js +66 -0
  47. package/dist/canId.js.map +1 -0
  48. package/dist/canId.test.d.ts +2 -0
  49. package/dist/canId.test.d.ts.map +1 -0
  50. package/dist/canId.test.js +58 -0
  51. package/dist/canId.test.js.map +1 -0
  52. package/dist/canbus.d.ts +17 -0
  53. package/dist/canbus.d.ts.map +1 -0
  54. package/dist/canbus.js +261 -0
  55. package/dist/canbus.js.map +1 -0
  56. package/dist/candevice.d.ts +23 -0
  57. package/dist/candevice.d.ts.map +1 -0
  58. package/dist/candevice.js +36 -0
  59. package/dist/candevice.js.map +1 -0
  60. package/dist/codes.d.ts +2 -0
  61. package/dist/codes.d.ts.map +1 -0
  62. package/dist/codes.js +24 -0
  63. package/dist/codes.js.map +1 -0
  64. package/dist/discovery.d.ts +17 -0
  65. package/dist/discovery.d.ts.map +1 -0
  66. package/dist/discovery.js +119 -0
  67. package/dist/discovery.js.map +1 -0
  68. package/dist/fromPgn.d.ts +51 -0
  69. package/dist/fromPgn.d.ts.map +1 -0
  70. package/dist/fromPgn.js +989 -0
  71. package/dist/fromPgn.js.map +1 -0
  72. package/dist/fromPgnStream.d.ts +17 -0
  73. package/dist/fromPgnStream.d.ts.map +1 -0
  74. package/dist/fromPgnStream.js +47 -0
  75. package/dist/fromPgnStream.js.map +1 -0
  76. package/dist/ikonvert.d.ts +17 -0
  77. package/dist/ikonvert.d.ts.map +1 -0
  78. package/dist/ikonvert.js +224 -0
  79. package/dist/ikonvert.js.map +1 -0
  80. package/dist/index.d.ts +31 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +60 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/n2k-actisense.d.ts +24 -0
  85. package/dist/n2k-actisense.d.ts.map +1 -0
  86. package/dist/n2k-actisense.js +126 -0
  87. package/dist/n2k-actisense.js.map +1 -0
  88. package/dist/n2k-actisense.test.d.ts +2 -0
  89. package/dist/n2k-actisense.test.d.ts.map +1 -0
  90. package/dist/n2k-actisense.test.js +49 -0
  91. package/dist/n2k-actisense.test.js.map +1 -0
  92. package/dist/n2kDevice.d.ts +44 -0
  93. package/dist/n2kDevice.d.ts.map +1 -0
  94. package/dist/n2kDevice.js +436 -0
  95. package/dist/n2kDevice.js.map +1 -0
  96. package/dist/pgns.d.ts +22 -0
  97. package/dist/pgns.d.ts.map +1 -0
  98. package/dist/pgns.js +157 -0
  99. package/dist/pgns.js.map +1 -0
  100. package/dist/pgns.test.d.ts +2 -0
  101. package/dist/pgns.test.d.ts.map +1 -0
  102. package/dist/pgns.test.js +14 -0
  103. package/dist/pgns.test.js.map +1 -0
  104. package/dist/simpleCan.d.ts +17 -0
  105. package/dist/simpleCan.d.ts.map +1 -0
  106. package/dist/simpleCan.js +125 -0
  107. package/dist/simpleCan.js.map +1 -0
  108. package/dist/stringMsg.d.ts +33 -0
  109. package/dist/stringMsg.d.ts.map +1 -0
  110. package/dist/stringMsg.js +359 -0
  111. package/dist/stringMsg.js.map +1 -0
  112. package/dist/stringMsg.test.d.ts +2 -0
  113. package/dist/stringMsg.test.d.ts.map +1 -0
  114. package/dist/stringMsg.test.js +244 -0
  115. package/dist/stringMsg.test.js.map +1 -0
  116. package/dist/toPgn.d.ts +34 -0
  117. package/dist/toPgn.d.ts.map +1 -0
  118. package/dist/toPgn.js +500 -0
  119. package/dist/toPgn.js.map +1 -0
  120. package/dist/utilities.d.ts +28 -0
  121. package/dist/utilities.d.ts.map +1 -0
  122. package/dist/utilities.js +159 -0
  123. package/dist/utilities.js.map +1 -0
  124. package/dist/utilities.test.d.ts +2 -0
  125. package/dist/utilities.test.d.ts.map +1 -0
  126. package/dist/utilities.test.js +9 -0
  127. package/dist/utilities.test.js.map +1 -0
  128. package/dist/venus-mqtt.d.ts +11 -0
  129. package/dist/venus-mqtt.d.ts.map +1 -0
  130. package/dist/venus-mqtt.js +100 -0
  131. package/dist/venus-mqtt.js.map +1 -0
  132. package/dist/venus.d.ts +11 -0
  133. package/dist/venus.d.ts.map +1 -0
  134. package/dist/venus.js +71 -0
  135. package/dist/venus.js.map +1 -0
  136. package/dist/w2k01.d.ts +17 -0
  137. package/dist/w2k01.d.ts.map +1 -0
  138. package/dist/w2k01.js +119 -0
  139. package/dist/w2k01.js.map +1 -0
  140. package/dist/yddevice.d.ts +25 -0
  141. package/dist/yddevice.d.ts.map +1 -0
  142. package/dist/yddevice.js +43 -0
  143. package/dist/yddevice.js.map +1 -0
  144. package/dist/ydgw02.d.ts +17 -0
  145. package/dist/ydgw02.d.ts.map +1 -0
  146. package/dist/ydgw02.js +163 -0
  147. package/dist/ydgw02.js.map +1 -0
  148. package/dist/ydvr.d.ts +17 -0
  149. package/dist/ydvr.d.ts.map +1 -0
  150. package/dist/ydvr.js +120 -0
  151. package/dist/ydvr.js.map +1 -0
  152. package/eslint.config.js +58 -0
  153. package/jest.config.js +5 -0
  154. package/lib/actisense-serial.ts +644 -0
  155. package/lib/bin/actisense-file.ts +53 -0
  156. package/lib/bin/actisense-n2k-tcp.ts +50 -0
  157. package/lib/bin/actisense-serialjs.ts +55 -0
  158. package/{bin/analyzerjs → lib/bin/analyzerjs.ts} +16 -15
  159. package/{bin/candumpjs → lib/bin/candumpjs.ts} +19 -20
  160. package/lib/bin/cansend.ts +131 -0
  161. package/lib/bin/ikonvert-serial.ts +44 -0
  162. package/{bin/to-pgn → lib/bin/to-pgn.ts} +23 -17
  163. package/lib/bin/ydvr-file +33 -0
  164. package/lib/canId.test.js +35 -15
  165. package/lib/canId.ts +84 -0
  166. package/lib/canbus.ts +293 -0
  167. package/lib/candevice.ts +41 -0
  168. package/lib/codes.ts +21 -0
  169. package/lib/{discovery.js → discovery.ts} +32 -30
  170. package/lib/fromPgn.ts +1207 -0
  171. package/lib/{fromPgnStream.js → fromPgnStream.ts} +17 -12
  172. package/lib/{ikonvert.js → ikonvert.ts} +99 -94
  173. package/lib/index.ts +48 -0
  174. package/lib/n2k-actisense.test.js +31 -32
  175. package/lib/n2k-actisense.ts +152 -0
  176. package/lib/n2kDevice.ts +516 -0
  177. package/lib/pgns.test.ts +12 -0
  178. package/lib/pgns.ts +191 -0
  179. package/lib/simpleCan.ts +140 -0
  180. package/lib/stringMsg.test.js +58 -41
  181. package/lib/stringMsg.ts +464 -0
  182. package/lib/toPgn.ts +597 -0
  183. package/lib/utilities.ts +169 -0
  184. package/lib/venus-mqtt.js +69 -73
  185. package/lib/venus.js +8 -8
  186. package/lib/w2k01.ts +142 -0
  187. package/lib/{yddevice.js → yddevice.ts} +18 -16
  188. package/lib/{ydgw02.js → ydgw02.ts} +58 -51
  189. package/lib/ydvr.js +65 -147
  190. package/package.json +54 -18
  191. package/tsconfig.json +19 -0
  192. package/tsconfig.tsbuildinfo +1 -0
  193. package/CHANGELOG.md +0 -101
  194. package/bin/actisense-file +0 -47
  195. package/bin/actisense-n2k-tcp +0 -45
  196. package/bin/actisense-serialjs +0 -52
  197. package/bin/candumpanalyzerjs +0 -29
  198. package/bin/cansend +0 -130
  199. package/bin/ikonvert-serial +0 -50
  200. package/bin/ydgw-analyzerjs +0 -36
  201. package/bin/ydvr-file +0 -33
  202. package/index.js +0 -53
  203. package/ios_canboat.js/main.js +0 -2
  204. package/ios_canboat.js/main.js.LICENSE.txt +0 -17
  205. package/ios_canboat.js/main.js.map +0 -1
  206. package/lib/canId.js +0 -64
  207. package/lib/canbus.js +0 -278
  208. package/lib/candevice.js +0 -51
  209. package/lib/codes.js +0 -69
  210. package/lib/codes.test.js +0 -17
  211. package/lib/codesMfgs.json +0 -166
  212. package/lib/fromPgn.js +0 -1027
  213. package/lib/n2k-actisense.js +0 -108
  214. package/lib/n2kDevice.js +0 -390
  215. package/lib/pgns.js +0 -196
  216. package/lib/pgns.test.js +0 -13
  217. package/lib/serial.js +0 -647
  218. package/lib/simpleCan.js +0 -105
  219. package/lib/stringMsg.js +0 -339
  220. package/lib/toPgn.js +0 -518
  221. package/lib/utilities.js +0 -111
  222. package/lib/w2k01.js +0 -129
@@ -0,0 +1,516 @@
1
+ /**
2
+ * Copyright 2025 Scott Bender (scott@scottbender.net)
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ PGN,
19
+ PGN_60928,
20
+ PGN_59904,
21
+ PGN_126208,
22
+ PGN_126208_Acknowledge,
23
+ PGN_126208_Command,
24
+ PGN_126996,
25
+ PGN_126993,
26
+ PGN_59392,
27
+ PGN_126464,
28
+ PgnListFunction,
29
+ GroupFunction,
30
+ PgnErrorCode,
31
+ TransmissionInterval,
32
+ ControllerState,
33
+ IsoControl
34
+ } from '@canboat/ts-pgns'
35
+ import { EventEmitter } from 'node:events'
36
+ import _ from 'lodash'
37
+ import { Uint64LE } from 'int64-buffer'
38
+ import { defaultTransmitPGNs } from './codes'
39
+ import { toPgn } from './toPgn'
40
+ import packageJson from '../package.json'
41
+ import { getPersistedData, savePersistedData } from './utilities'
42
+ import { createDebug } from './utilities'
43
+
44
+ const deviceTransmitPGNs = [60928, 59904, 126996, 126464]
45
+
46
+ export class N2kDevice extends EventEmitter {
47
+ addressClaim: any
48
+ productInfo: any
49
+ configurationInfo: any
50
+ options: any
51
+ address: number
52
+ cansend: boolean
53
+ foundConflict: boolean
54
+ heartbeatCounter: number
55
+ devices: any
56
+ sentAvailable: boolean
57
+ addressClaimDetectionTime: number
58
+ transmitPGNs: number[]
59
+ addressClaimSentAt?: number
60
+ addressClaimChecker?: any
61
+ heartbeatInterval?: any
62
+ debug: any
63
+
64
+ constructor(options: any, debugName: string) {
65
+ super()
66
+
67
+ this.options = options === undefined ? {} : options
68
+ this.debug = createDebug(debugName, options)
69
+
70
+ let uniqueNumber: number
71
+ if (options.uniqueNumber !== undefined) {
72
+ uniqueNumber = options.uniqueNumber
73
+ } else {
74
+ uniqueNumber = this.getPersistedData('uniqueNumber')
75
+ if (uniqueNumber === undefined) {
76
+ uniqueNumber = Math.floor(Math.random() * Math.floor(2097151))
77
+ this.savePersistedData('uniqueNumber', uniqueNumber)
78
+ }
79
+ }
80
+
81
+ if (options.addressClaim) {
82
+ this.addressClaim = options.addressClaim
83
+ this.addressClaim.pgn = 60928
84
+ this.addressClaim.dst = 255
85
+ this.addressClaim.prio = 6
86
+ } else {
87
+ this.addressClaim = {
88
+ pgn: 60928,
89
+ dst: 255,
90
+ prio: 6,
91
+ 'Manufacturer Code': 999,
92
+ 'Device Function': 130, // PC gateway
93
+ 'Device Class': 25, // Inter/Intranetwork Device
94
+ 'Device Instance Lower': 0,
95
+ 'Device Instance Upper': 0,
96
+ 'System Instance': 0,
97
+ 'Industry Group': 4, // Marine
98
+ Reserved1: 1,
99
+ Reserved2: 2
100
+ }
101
+
102
+ this.addressClaim['Unique Number'] = uniqueNumber
103
+ }
104
+
105
+ const version = packageJson ? packageJson.version : '1.0'
106
+
107
+ if (options.productInfo) {
108
+ this.productInfo = options.productInfo
109
+ this.productInfo.pgn = 126996
110
+ this.productInfo.dst = 255
111
+ } else {
112
+ this.productInfo = {
113
+ pgn: 126996,
114
+ dst: 255,
115
+ 'NMEA 2000 Version': 1300,
116
+ 'Product Code': 667, // Just made up..
117
+ 'Model ID': 'Signal K',
118
+ 'Model Version': getModelVersion(options),
119
+ 'Model Serial Code': uniqueNumber.toString(),
120
+ 'Certification Level': 0,
121
+ 'Load Equivalency': 1
122
+ }
123
+ }
124
+
125
+ this.productInfo['Software Version Code'] = version
126
+
127
+ if (options.serverVersion && options.serverUrl) {
128
+ this.configurationInfo = {
129
+ pgn: 126998,
130
+ dst: 255,
131
+ 'Installation Description #1': options.serverUrl,
132
+ 'Installation Description #2': options.serverDescription,
133
+ 'Manufacturer Information': options.serverVersion
134
+ }
135
+ }
136
+
137
+ let address: number | undefined = undefined
138
+
139
+ address = this.getPersistedData('lastAddress')
140
+
141
+ if (address === undefined) {
142
+ address = _.isUndefined(options.preferredAddress)
143
+ ? 100
144
+ : options.preferredAddress
145
+ }
146
+ this.address = address!
147
+ this.cansend = false
148
+ this.foundConflict = false
149
+ this.heartbeatCounter = 0
150
+ this.devices = {}
151
+ this.sentAvailable = false
152
+ this.addressClaimDetectionTime =
153
+ options.addressClaimDetectionTime !== undefined
154
+ ? options.addressClaimDetectionTime
155
+ : 5000
156
+
157
+ if (!options.disableDefaultTransmitPGNs) {
158
+ this.transmitPGNs = _.union(deviceTransmitPGNs, defaultTransmitPGNs)
159
+ } else {
160
+ this.transmitPGNs = [...deviceTransmitPGNs]
161
+ }
162
+
163
+ if (this.options.transmitPGNs) {
164
+ this.transmitPGNs = _.union(this.transmitPGNs, this.options.transmitPGNs)
165
+ }
166
+ }
167
+
168
+ start() {
169
+ sendISORequest(this, 60928, 254)
170
+ setTimeout(() => {
171
+ sendAddressClaim(this)
172
+ }, 1000)
173
+ }
174
+
175
+ getPersistedData(key: string) {
176
+ try {
177
+ return getPersistedData(this.options, this.options.providerId, key)
178
+ } catch (err: any) {
179
+ this.debug('reading persisted data %o', err)
180
+ if (err.code !== 'ENOENT') {
181
+ console.error(err)
182
+ this.setError(err.message)
183
+ }
184
+ }
185
+ }
186
+
187
+ savePersistedData(key: string, value: any) {
188
+ try {
189
+ savePersistedData(this.options, this.options.providerId, key, value)
190
+ } catch (err: any) {
191
+ console.error(err)
192
+ this.setError(err.message)
193
+ }
194
+ }
195
+
196
+ setStatus(msg: string) {
197
+ if (this.options.app && this.options.app.setPluginStatus) {
198
+ this.options.app.setProviderStatus(this.options.providerId, msg)
199
+ }
200
+ }
201
+
202
+ setError(msg: string) {
203
+ if (this.options.app && this.options.app.setPluginStatus) {
204
+ this.options.app.setProviderError(this.options.providerId, msg)
205
+ }
206
+ }
207
+
208
+ n2kMessage(pgn: PGN) {
209
+ if (pgn.dst == 255 || pgn.dst == this.address) {
210
+ try {
211
+ if (pgn.pgn == 59904) {
212
+ handleISORequest(this, pgn)
213
+ } else if (pgn.pgn == 126208) {
214
+ handleGroupFunction(this, pgn as PGN_126208)
215
+ } else if (pgn.pgn == 60928) {
216
+ handleISOAddressClaim(this, pgn as PGN_60928)
217
+ } else if (pgn.pgn == 126996) {
218
+ handleProductInformation(this, pgn)
219
+ }
220
+ } catch (err) {
221
+ console.error(err)
222
+ }
223
+
224
+ /*
225
+ var handler = this.handlers[pgn.pgn.toString()]
226
+ if ( pgn.dst == this.address )
227
+ debug(`handler ${handler}`)
228
+ if ( _.isFunction(handler) ) {
229
+ debug(`got handled PGN %j ${handled}`, pgn)
230
+ handler(pgn)
231
+ }
232
+ */
233
+ }
234
+ }
235
+
236
+ sendPGN(_pgn: PGN, _src: number | undefined = undefined) {}
237
+ }
238
+
239
+ function getModelVersion(options: any) {
240
+ if (options.app?.config?.getExternalHostname !== undefined) {
241
+ return `${options.app.config.ssl ? 'https' : 'http'}://${options.app.config.getExternalHostname()}:${options.app.config.getExternalPort()}`
242
+ } else {
243
+ return 'canboatjs'
244
+ }
245
+ }
246
+
247
+ function handleISORequest(device: N2kDevice, n2kMsg: PGN_59904) {
248
+ device.debug('handleISORequest %j', n2kMsg)
249
+
250
+ const PGN = Number(n2kMsg.fields.pgn)
251
+
252
+ switch (PGN) {
253
+ case 126996: // Product Information request
254
+ sendProductInformation(device)
255
+ break
256
+ case 126998: // Config Information request
257
+ sendConfigInformation(device)
258
+ break
259
+ case 60928: // ISO address claim request
260
+ device.debug('sending address claim %j', device.addressClaim)
261
+ device.sendPGN(device.addressClaim as PGN)
262
+ break
263
+ case 126464:
264
+ sendPGNList(device, n2kMsg.src!)
265
+ break
266
+ default:
267
+ if (!device.options.disableNAKs) {
268
+ device.debug(`Got unsupported ISO request for PGN ${PGN}. Sending NAK.`)
269
+ sendNAKAcknowledgement(device, n2kMsg.src!, PGN)
270
+ }
271
+ }
272
+ }
273
+
274
+ function handleGroupFunction(device: N2kDevice, n2kMsg: PGN_126208) {
275
+ device.debug('handleGroupFunction %j', n2kMsg)
276
+ const functionCode = n2kMsg.fields.functionCode
277
+ if (functionCode === 'Request') {
278
+ handleRequestGroupFunction(device, n2kMsg)
279
+ } else if (functionCode === 'Command') {
280
+ handleCommandGroupFunction(device, n2kMsg)
281
+ } else {
282
+ device.debug('Got unsupported Group Function PGN: %j', n2kMsg)
283
+ }
284
+
285
+ function handleRequestGroupFunction(device: N2kDevice, n2kMsg: PGN_126208) {
286
+ if (!device.options.disableNAKs) {
287
+ // We really don't support group function requests for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
288
+
289
+ const PGN = n2kMsg.fields.pgn
290
+
291
+ device.debug(
292
+ "Sending 'PGN Not Supported' Group Function response for requested PGN",
293
+ PGN
294
+ )
295
+
296
+ const acknowledgement: PGN_126208_Acknowledge = {
297
+ pgn: 126208,
298
+ dst: n2kMsg.src!,
299
+ fields: {
300
+ functionCode: GroupFunction.Acknowledge,
301
+ pgn: PGN,
302
+ pgnErrorCode: PgnErrorCode.NotSupported,
303
+ transmissionIntervalPriorityErrorCode:
304
+ TransmissionInterval.Acknowledge,
305
+ numberOfParameters: 0,
306
+ list: []
307
+ }
308
+ }
309
+ device.sendPGN(acknowledgement)
310
+ }
311
+ }
312
+
313
+ function handleCommandGroupFunction(
314
+ device: N2kDevice,
315
+ n2kMsg: PGN_126208_Command
316
+ ) {
317
+ if (!device.options.disableNAKs) {
318
+ // We really don't support group function commands for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
319
+
320
+ const PGN = n2kMsg.fields.pgn
321
+
322
+ device.debug(
323
+ "Sending 'PGN Not Supported' Group Function response for commanded PGN",
324
+ PGN
325
+ )
326
+
327
+ const acknowledgement: PGN_126208_Acknowledge = {
328
+ pgn: 126208,
329
+ dst: n2kMsg.src!,
330
+ fields: {
331
+ functionCode: GroupFunction.Acknowledge,
332
+ pgn: PGN,
333
+ pgnErrorCode: PgnErrorCode.NotSupported,
334
+ transmissionIntervalPriorityErrorCode:
335
+ TransmissionInterval.Acknowledge,
336
+ numberOfParameters: 0,
337
+ list: []
338
+ }
339
+ }
340
+ device.sendPGN(acknowledgement)
341
+ }
342
+ }
343
+ }
344
+
345
+ function handleISOAddressClaim(device: N2kDevice, n2kMsg: PGN_60928) {
346
+ if (n2kMsg.src != device.address) {
347
+ if (!device.devices[n2kMsg.src!]) {
348
+ device.debug(`registering device ${n2kMsg.src}`)
349
+ device.devices[n2kMsg.src!] = { addressClaim: n2kMsg }
350
+ if (device.cansend) {
351
+ //sendISORequest(device, 126996, undefined, n2kMsg.src)
352
+ }
353
+ }
354
+ return
355
+ }
356
+
357
+ device.debug('Checking ISO address claim. %j', n2kMsg)
358
+
359
+ const uint64ValueFromReceivedClaim = getISOAddressClaimAsUint64(n2kMsg)
360
+ const uint64ValueFromOurOwnClaim = getISOAddressClaimAsUint64(
361
+ device.addressClaim
362
+ )
363
+
364
+ if (uint64ValueFromOurOwnClaim < uint64ValueFromReceivedClaim) {
365
+ device.debug(
366
+ `Address conflict detected! Kept our address as ${device.address}.`
367
+ )
368
+ sendAddressClaim(device) // We have smaller address claim data -> we can keep our address -> re-claim it
369
+ } else if (uint64ValueFromOurOwnClaim > uint64ValueFromReceivedClaim) {
370
+ device.foundConflict = true
371
+ increaseOwnAddress(device) // We have bigger address claim data -> we have to change our address
372
+ device.debug(
373
+ `Address conflict detected! trying address ${device.address}.`
374
+ )
375
+ sendAddressClaim(device)
376
+ }
377
+ }
378
+
379
+ function increaseOwnAddress(device: N2kDevice) {
380
+ const start = device.address
381
+ do {
382
+ device.address = (device.address + 1) % 253
383
+ } while (device.address != start && device.devices[device.address])
384
+ }
385
+
386
+ function handleProductInformation(device: N2kDevice, n2kMsg: PGN_126996) {
387
+ if (!device.devices[n2kMsg.src!]) {
388
+ device.devices[n2kMsg.src!] = {}
389
+ }
390
+ device.debug('got product information %j', n2kMsg)
391
+ device.devices[n2kMsg.src!].productInformation = n2kMsg
392
+ }
393
+
394
+ function sendHeartbeat(device: N2kDevice) {
395
+ device.heartbeatCounter = device.heartbeatCounter + 1
396
+ if (device.heartbeatCounter > 252) {
397
+ device.heartbeatCounter = 0
398
+ }
399
+
400
+ const hb: PGN_126993 = {
401
+ pgn: 126993,
402
+ dst: 255,
403
+ prio: 7,
404
+ fields: {
405
+ dataTransmitOffset: 60,
406
+ sequenceCounter: device.heartbeatCounter,
407
+ controller1State: ControllerState.ErrorActive
408
+ }
409
+ }
410
+
411
+ device.sendPGN(hb)
412
+ }
413
+
414
+ function sendAddressClaim(device: N2kDevice) {
415
+ if (device.devices[device.address]) {
416
+ //someone already has this address, so find a free one
417
+ increaseOwnAddress(device)
418
+ }
419
+ device.debug(`Sending address claim ${device.address}`)
420
+ device.sendPGN(device.addressClaim)
421
+ device.setStatus(`Claimed address ${device.address}`)
422
+ device.addressClaimSentAt = Date.now()
423
+ if (device.addressClaimChecker) {
424
+ clearTimeout(device.addressClaimChecker)
425
+ }
426
+
427
+ device.addressClaimChecker = setTimeout(() => {
428
+ //if ( Date.now() - device.addressClaimSentAt > 1000 ) {
429
+ //device.addressClaimChecker = null
430
+ device.debug('claimed address %d', device.address)
431
+
432
+ device.savePersistedData('lastAddress', device.address)
433
+
434
+ device.cansend = true
435
+ if (!device.sentAvailable) {
436
+ if (device.options.app) {
437
+ device.options.app.emit('nmea2000OutAvailable')
438
+ }
439
+ device.emit('nmea2000OutAvailable')
440
+ device.sentAvailable = true
441
+ }
442
+ sendISORequest(device, 126996)
443
+ if (!device.heartbeatInterval) {
444
+ device.heartbeatInterval = setInterval(() => {
445
+ sendHeartbeat(device)
446
+ }, 60 * 1000)
447
+ }
448
+ //}
449
+ }, device.addressClaimDetectionTime)
450
+ }
451
+
452
+ function sendISORequest(
453
+ device: N2kDevice,
454
+ pgn: number,
455
+ src: number | undefined = undefined,
456
+ dst = 255
457
+ ) {
458
+ device.debug(`Sending iso request for ${pgn} to ${dst}`)
459
+
460
+ const isoRequest: PGN_59904 = {
461
+ pgn: 59904,
462
+ dst: dst,
463
+ fields: {
464
+ pgn
465
+ }
466
+ }
467
+ device.sendPGN(isoRequest, src)
468
+ }
469
+
470
+ function sendProductInformation(device: N2kDevice) {
471
+ device.debug('Sending product info %j', device.productInfo)
472
+
473
+ device.sendPGN(device.productInfo)
474
+ }
475
+
476
+ function sendConfigInformation(device: N2kDevice) {
477
+ if (device.configurationInfo) {
478
+ device.debug('Sending config info..')
479
+ device.sendPGN(device.configurationInfo)
480
+ }
481
+ }
482
+
483
+ function sendNAKAcknowledgement(
484
+ device: N2kDevice,
485
+ src: number,
486
+ requestedPGN: number
487
+ ) {
488
+ const acknowledgement: PGN_59392 = {
489
+ pgn: 59392,
490
+ dst: src,
491
+ fields: {
492
+ control: IsoControl.Ack,
493
+ groupFunction: 255,
494
+ pgn: requestedPGN
495
+ }
496
+ }
497
+ device.sendPGN(acknowledgement)
498
+ }
499
+
500
+ function sendPGNList(device: N2kDevice, dst: number) {
501
+ //FIXME: for now, adding everything that signalk-to-nmea2000 supports
502
+ //need a way for plugins, etc. to register the pgns they provide
503
+ const pgnList: PGN_126464 = {
504
+ pgn: 126464,
505
+ dst,
506
+ fields: {
507
+ functionCode: PgnListFunction.TransmitPgnList,
508
+ list: device.transmitPGNs
509
+ }
510
+ }
511
+ device.sendPGN(pgnList)
512
+ }
513
+
514
+ function getISOAddressClaimAsUint64(pgn: any) {
515
+ return new Uint64LE(toPgn(pgn)!)
516
+ }
@@ -0,0 +1,12 @@
1
+ import { getPgn } from './pgns'
2
+
3
+ describe('getPgn', () => {
4
+ test('Return info array about a pgn number', () => {
5
+ const pgns = getPgn(60928)
6
+ expect(pgns !== undefined)
7
+ if (pgns !== undefined) {
8
+ expect(pgns.length > 0)
9
+ expect(pgns[0].Description).toBe('ISO Address Claim')
10
+ }
11
+ })
12
+ })
package/lib/pgns.ts ADDED
@@ -0,0 +1,191 @@
1
+ import {
2
+ getPGN,
3
+ EnumBase,
4
+ Enumeration,
5
+ BitEnumeration,
6
+ FieldTypeEnumeration,
7
+ getEnumerations,
8
+ getFieldTypeEnumerations,
9
+ getBitEnumerations
10
+ } from '@canboat/ts-pgns'
11
+ import _ from 'lodash'
12
+ import { createDebug } from './utilities'
13
+
14
+ const debug = createDebug('canboatjs:pgns')
15
+
16
+ //const enumValues : {[key:string]: {[key:number]: string}} = {}
17
+ //const enumNames : {[key:string]: {[key:string]: number}} = {}
18
+
19
+ const enumValues: any = {}
20
+ const enumNames: any = {}
21
+
22
+ function getEnumeration(name: string) {
23
+ const enumeration: Enumeration = lookupEnumerations[name] as Enumeration
24
+ if (enumeration) {
25
+ let vmap: any = enumValues[name]
26
+ if (!vmap) {
27
+ vmap = {}
28
+ enumValues[name] = vmap
29
+ enumeration.EnumValues.forEach((enumPair) => {
30
+ vmap[enumPair.Value] = enumPair.Name
31
+ })
32
+
33
+ const nmap: any = {}
34
+ enumNames[name] = nmap
35
+ enumeration.EnumValues.forEach((enumPair) => {
36
+ nmap[enumPair.Name] = enumPair.Value
37
+ })
38
+ }
39
+ }
40
+ return enumeration
41
+ }
42
+
43
+ const ftEnumValues: any = {}
44
+ const ftEnumNames: any = {}
45
+ const ftEnumBits: any = {}
46
+
47
+ function getFieldTypeEnumeration(name: string) {
48
+ const enumeration = lookupFieldTypeEnumerations[name] as FieldTypeEnumeration
49
+ if (enumeration) {
50
+ let vmap = ftEnumValues[name]
51
+ if (!vmap) {
52
+ vmap = {}
53
+ ftEnumValues[name] = vmap
54
+ enumeration.EnumFieldTypeValues.forEach((enumPair) => {
55
+ vmap[enumPair.value] = enumPair.name
56
+ })
57
+
58
+ const nmap: any = {}
59
+ ftEnumNames[name] = nmap
60
+ enumeration.EnumFieldTypeValues.forEach((enumPair) => {
61
+ nmap[enumPair.name] = enumPair.value
62
+ })
63
+
64
+ const bmap: any = {}
65
+ ftEnumBits[name] = bmap
66
+ enumeration.EnumFieldTypeValues.forEach((enumPair) => {
67
+ bmap[enumPair.value] = Number(enumPair.Bits)
68
+ })
69
+ }
70
+ }
71
+ return enumeration
72
+ }
73
+
74
+ const bEnumValues: any = {}
75
+ const bEnumNames: any = {}
76
+
77
+ function getBitEnumeration(name: string) {
78
+ const enumeration = lookupBitEnumerations[name] as BitEnumeration
79
+ if (enumeration) {
80
+ let nmap = bEnumNames[name]
81
+ if (!nmap) {
82
+ nmap = {}
83
+ bEnumNames[name] = nmap
84
+ enumeration.EnumBitValues.forEach((enumPair) => {
85
+ nmap[enumPair.Bit] = enumPair.Name
86
+ })
87
+
88
+ const vmap: any = {}
89
+ bEnumValues[name] = vmap
90
+ enumeration.EnumBitValues.forEach((enumPair) => {
91
+ vmap[enumPair.Name] = Number(enumPair.Bit)
92
+ })
93
+ }
94
+ }
95
+ return enumeration
96
+ }
97
+
98
+ export function lookupEnumerationName(enumName: string, value: number) {
99
+ const enumeration = getEnumeration(enumName)
100
+ return enumeration && enumValues[enumName][value]
101
+ }
102
+
103
+ export function lookupEnumerationValue(enumName: string, name: string) {
104
+ const enumeration = getEnumeration(enumName)
105
+ return enumeration && enumNames[enumName][name]
106
+ }
107
+
108
+ export function lookupFieldTypeEnumerationName(
109
+ enumName: string,
110
+ value: number
111
+ ) {
112
+ const enumeration = getFieldTypeEnumeration(enumName)
113
+ return enumeration && ftEnumValues[enumName][value]
114
+ }
115
+
116
+ export function lookupFieldTypeEnumerationBits(
117
+ enumName: string,
118
+ value: number
119
+ ) {
120
+ const enumeration = getFieldTypeEnumeration(enumName)
121
+ return enumeration && ftEnumBits[enumName][value]
122
+ }
123
+
124
+ export function lookupFieldTypeEnumerationValue(
125
+ enumName: string,
126
+ name: string
127
+ ) {
128
+ const enumeration = getFieldTypeEnumeration(enumName)
129
+ return enumeration && ftEnumNames[enumName][name]
130
+ }
131
+
132
+ export function lookupBitEnumerationName(enumName: string, value: number) {
133
+ const enumeration = getBitEnumeration(enumName)
134
+ return enumeration && bEnumNames[enumName][value]
135
+ }
136
+
137
+ export function lookupBitEnumerationValue(enumName: string, name: number) {
138
+ const enumeration = getBitEnumeration(enumName)
139
+ return enumeration && bEnumValues[enumName][name]
140
+ }
141
+
142
+ function organizeEnumerations(enums: EnumBase[]): { [key: string]: EnumBase } {
143
+ const map: any = {}
144
+ enums.forEach((e) => {
145
+ map[e.Name] = e
146
+ })
147
+ return map
148
+ }
149
+
150
+ export const lookupEnumerations = organizeEnumerations(getEnumerations())
151
+ export const lookupFieldTypeEnumerations = organizeEnumerations(
152
+ getFieldTypeEnumerations()
153
+ )
154
+ export const lookupBitEnumerations = organizeEnumerations(getBitEnumerations())
155
+ export const getPgn = getPGN
156
+ export const customPgns: any = {}
157
+
158
+ export const addCustomPgns = (pgns: any, setter: any) => {
159
+ pgns.PGNs.forEach((pgn: any) => {
160
+ if (!customPgns[pgn.PGN]) {
161
+ customPgns[pgn.PGN] = {
162
+ definitions: [],
163
+ callbacks: []
164
+ }
165
+ }
166
+
167
+ customPgns[pgn.PGN].definitions.push(pgn)
168
+
169
+ /*
170
+ if ( pgn.calllback ) {
171
+ customPgns[pgn.PGN].callbacks.push()
172
+ }
173
+ */
174
+
175
+ debug('registered custom pgn %d by %s', pgn.PGN, setter)
176
+ })
177
+
178
+ if (pgns.LookupEnumerations) {
179
+ pgns.LookupEnumerations.forEach((e: any) => {
180
+ if (!lookupEnumerations[e.Name]) {
181
+ lookupEnumerations[e.Name] = e
182
+ } else {
183
+ debug(`enumeration ${e.Name} already exists`)
184
+ }
185
+ })
186
+ }
187
+ }
188
+
189
+ export const getCustomPgn = (pgnNum: number) => {
190
+ return customPgns[pgnNum]
191
+ }