@canboat/canboatjs 3.0.2 → 3.1.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.
Files changed (222) hide show
  1. package/.github/workflows/test.yml +2 -2
  2. package/.github/workflows/test_canboat_changes.yml +91 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.mocharc.js +7 -0
  5. package/.prettierrc.json +5 -0
  6. package/README.md +2 -7
  7. package/dist/actisense-serial.d.ts +17 -0
  8. package/dist/actisense-serial.d.ts.map +1 -0
  9. package/dist/actisense-serial.js +569 -0
  10. package/dist/actisense-serial.js.map +1 -0
  11. package/dist/bin/actisense-file.d.ts +3 -0
  12. package/dist/bin/actisense-file.d.ts.map +1 -0
  13. package/dist/bin/actisense-file.js +49 -0
  14. package/dist/bin/actisense-file.js.map +1 -0
  15. package/dist/bin/actisense-n2k-tcp.d.ts +3 -0
  16. package/dist/bin/actisense-n2k-tcp.d.ts.map +1 -0
  17. package/dist/bin/actisense-n2k-tcp.js +46 -0
  18. package/dist/bin/actisense-n2k-tcp.js.map +1 -0
  19. package/dist/bin/actisense-serialjs.d.ts +3 -0
  20. package/dist/bin/actisense-serialjs.d.ts.map +1 -0
  21. package/dist/bin/actisense-serialjs.js +51 -0
  22. package/dist/bin/actisense-serialjs.js.map +1 -0
  23. package/dist/bin/analyzerjs.d.ts +3 -0
  24. package/dist/bin/analyzerjs.d.ts.map +1 -0
  25. package/dist/bin/analyzerjs.js +61 -0
  26. package/dist/bin/analyzerjs.js.map +1 -0
  27. package/dist/bin/candumpanalyzerjs.d.ts +3 -0
  28. package/dist/bin/candumpanalyzerjs.d.ts.map +1 -0
  29. package/dist/bin/candumpanalyzerjs.js +31 -0
  30. package/dist/bin/candumpanalyzerjs.js.map +1 -0
  31. package/dist/bin/candumpjs.d.ts +3 -0
  32. package/dist/bin/candumpjs.d.ts.map +1 -0
  33. package/dist/bin/candumpjs.js +73 -0
  34. package/dist/bin/candumpjs.js.map +1 -0
  35. package/dist/bin/cansend.d.ts +3 -0
  36. package/dist/bin/cansend.d.ts.map +1 -0
  37. package/dist/bin/cansend.js +123 -0
  38. package/dist/bin/cansend.js.map +1 -0
  39. package/dist/bin/ikonvert-serial.d.ts +3 -0
  40. package/dist/bin/ikonvert-serial.d.ts.map +1 -0
  41. package/dist/bin/ikonvert-serial.js +36 -0
  42. package/dist/bin/ikonvert-serial.js.map +1 -0
  43. package/dist/bin/to-pgn.d.ts +3 -0
  44. package/dist/bin/to-pgn.d.ts.map +1 -0
  45. package/dist/bin/to-pgn.js +57 -0
  46. package/dist/bin/to-pgn.js.map +1 -0
  47. package/dist/canId.d.ts +21 -0
  48. package/dist/canId.d.ts.map +1 -0
  49. package/dist/canId.js +66 -0
  50. package/dist/canId.js.map +1 -0
  51. package/dist/canId.test.d.ts +2 -0
  52. package/dist/canId.test.d.ts.map +1 -0
  53. package/dist/canId.test.js +58 -0
  54. package/dist/canId.test.js.map +1 -0
  55. package/dist/canbus.d.ts +17 -0
  56. package/dist/canbus.d.ts.map +1 -0
  57. package/dist/canbus.js +261 -0
  58. package/dist/canbus.js.map +1 -0
  59. package/dist/candevice.d.ts +23 -0
  60. package/dist/candevice.d.ts.map +1 -0
  61. package/dist/candevice.js +38 -0
  62. package/dist/candevice.js.map +1 -0
  63. package/dist/codes.d.ts +2 -0
  64. package/dist/codes.d.ts.map +1 -0
  65. package/dist/codes.js +24 -0
  66. package/dist/codes.js.map +1 -0
  67. package/dist/discovery.d.ts +17 -0
  68. package/dist/discovery.d.ts.map +1 -0
  69. package/dist/discovery.js +119 -0
  70. package/dist/discovery.js.map +1 -0
  71. package/dist/fromPgn.d.ts +51 -0
  72. package/dist/fromPgn.d.ts.map +1 -0
  73. package/dist/fromPgn.js +989 -0
  74. package/dist/fromPgn.js.map +1 -0
  75. package/dist/fromPgnStream.d.ts +17 -0
  76. package/dist/fromPgnStream.d.ts.map +1 -0
  77. package/dist/fromPgnStream.js +47 -0
  78. package/dist/fromPgnStream.js.map +1 -0
  79. package/dist/ikonvert.d.ts +17 -0
  80. package/dist/ikonvert.d.ts.map +1 -0
  81. package/dist/ikonvert.js +224 -0
  82. package/dist/ikonvert.js.map +1 -0
  83. package/dist/index.d.ts +31 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +60 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/n2k-actisense.d.ts +24 -0
  88. package/dist/n2k-actisense.d.ts.map +1 -0
  89. package/dist/n2k-actisense.js +126 -0
  90. package/dist/n2k-actisense.js.map +1 -0
  91. package/dist/n2k-actisense.test.d.ts +2 -0
  92. package/dist/n2k-actisense.test.d.ts.map +1 -0
  93. package/dist/n2k-actisense.test.js +49 -0
  94. package/dist/n2k-actisense.test.js.map +1 -0
  95. package/dist/n2kDevice.d.ts +40 -0
  96. package/dist/n2kDevice.d.ts.map +1 -0
  97. package/dist/n2kDevice.js +387 -0
  98. package/dist/n2kDevice.js.map +1 -0
  99. package/dist/ncanbus.d.ts +17 -0
  100. package/dist/ncanbus.d.ts.map +1 -0
  101. package/dist/ncanbus.js +92 -0
  102. package/dist/ncanbus.js.map +1 -0
  103. package/dist/pgns.d.ts +22 -0
  104. package/dist/pgns.d.ts.map +1 -0
  105. package/dist/pgns.js +157 -0
  106. package/dist/pgns.js.map +1 -0
  107. package/dist/pgns.test.d.ts +2 -0
  108. package/dist/pgns.test.d.ts.map +1 -0
  109. package/dist/pgns.test.js +14 -0
  110. package/dist/pgns.test.js.map +1 -0
  111. package/dist/simpleCan.d.ts +17 -0
  112. package/dist/simpleCan.d.ts.map +1 -0
  113. package/dist/simpleCan.js +125 -0
  114. package/dist/simpleCan.js.map +1 -0
  115. package/dist/stringMsg.d.ts +33 -0
  116. package/dist/stringMsg.d.ts.map +1 -0
  117. package/dist/stringMsg.js +359 -0
  118. package/dist/stringMsg.js.map +1 -0
  119. package/dist/stringMsg.test.d.ts +2 -0
  120. package/dist/stringMsg.test.d.ts.map +1 -0
  121. package/dist/stringMsg.test.js +244 -0
  122. package/dist/stringMsg.test.js.map +1 -0
  123. package/dist/toPgn.d.ts +34 -0
  124. package/dist/toPgn.d.ts.map +1 -0
  125. package/dist/toPgn.js +500 -0
  126. package/dist/toPgn.js.map +1 -0
  127. package/dist/utilities.d.ts +26 -0
  128. package/dist/utilities.d.ts.map +1 -0
  129. package/dist/utilities.js +112 -0
  130. package/dist/utilities.js.map +1 -0
  131. package/dist/utilities.test.d.ts +2 -0
  132. package/dist/utilities.test.d.ts.map +1 -0
  133. package/dist/utilities.test.js +9 -0
  134. package/dist/utilities.test.js.map +1 -0
  135. package/dist/venus-mqtt.d.ts +11 -0
  136. package/dist/venus-mqtt.d.ts.map +1 -0
  137. package/dist/venus-mqtt.js +100 -0
  138. package/dist/venus-mqtt.js.map +1 -0
  139. package/dist/venus.d.ts +11 -0
  140. package/dist/venus.d.ts.map +1 -0
  141. package/dist/venus.js +71 -0
  142. package/dist/venus.js.map +1 -0
  143. package/dist/w2k01.d.ts +17 -0
  144. package/dist/w2k01.d.ts.map +1 -0
  145. package/dist/w2k01.js +119 -0
  146. package/dist/w2k01.js.map +1 -0
  147. package/dist/yddevice.d.ts +25 -0
  148. package/dist/yddevice.d.ts.map +1 -0
  149. package/dist/yddevice.js +45 -0
  150. package/dist/yddevice.js.map +1 -0
  151. package/dist/ydgw02.d.ts +17 -0
  152. package/dist/ydgw02.d.ts.map +1 -0
  153. package/dist/ydgw02.js +163 -0
  154. package/dist/ydgw02.js.map +1 -0
  155. package/dist/ydvr.d.ts +17 -0
  156. package/dist/ydvr.d.ts.map +1 -0
  157. package/dist/ydvr.js +120 -0
  158. package/dist/ydvr.js.map +1 -0
  159. package/eslint.config.js +58 -0
  160. package/jest.config.js +5 -0
  161. package/lib/actisense-serial.ts +644 -0
  162. package/lib/bin/actisense-file.ts +53 -0
  163. package/lib/bin/actisense-n2k-tcp.ts +50 -0
  164. package/lib/bin/actisense-serialjs.ts +55 -0
  165. package/{bin/analyzerjs → lib/bin/analyzerjs.ts} +16 -15
  166. package/{bin/candumpjs → lib/bin/candumpjs.ts} +19 -20
  167. package/lib/bin/cansend.ts +131 -0
  168. package/lib/bin/ikonvert-serial.ts +44 -0
  169. package/{bin/to-pgn → lib/bin/to-pgn.ts} +23 -17
  170. package/lib/bin/ydvr-file +33 -0
  171. package/lib/canId.test.js +35 -15
  172. package/lib/canId.ts +84 -0
  173. package/lib/canbus.ts +293 -0
  174. package/lib/{candevice.js → candevice.ts} +14 -21
  175. package/lib/codes.ts +21 -0
  176. package/lib/{discovery.js → discovery.ts} +32 -30
  177. package/lib/fromPgn.ts +1207 -0
  178. package/lib/{fromPgnStream.js → fromPgnStream.ts} +17 -12
  179. package/lib/{ikonvert.js → ikonvert.ts} +90 -85
  180. package/lib/index.ts +48 -0
  181. package/lib/n2k-actisense.test.js +31 -32
  182. package/lib/n2k-actisense.ts +152 -0
  183. package/lib/n2kDevice.ts +458 -0
  184. package/lib/pgns.test.ts +12 -0
  185. package/lib/pgns.ts +191 -0
  186. package/lib/simpleCan.ts +140 -0
  187. package/lib/stringMsg.test.js +58 -41
  188. package/lib/stringMsg.ts +464 -0
  189. package/lib/toPgn.ts +597 -0
  190. package/lib/{utilities.js → utilities.ts} +48 -37
  191. package/lib/venus-mqtt.js +69 -73
  192. package/lib/venus.js +8 -8
  193. package/lib/{w2k01.js → w2k01.ts} +57 -44
  194. package/lib/{yddevice.js → yddevice.ts} +18 -13
  195. package/lib/{ydgw02.js → ydgw02.ts} +55 -47
  196. package/lib/ydvr.js +65 -147
  197. package/package.json +54 -18
  198. package/tsconfig.json +19 -0
  199. package/tsconfig.tsbuildinfo +1 -0
  200. package/bin/actisense-file +0 -47
  201. package/bin/actisense-n2k-tcp +0 -45
  202. package/bin/actisense-serialjs +0 -52
  203. package/bin/candumpanalyzerjs +0 -29
  204. package/bin/cansend +0 -130
  205. package/bin/ikonvert-serial +0 -50
  206. package/bin/ydgw-analyzerjs +0 -36
  207. package/bin/ydvr-file +0 -33
  208. package/index.js +0 -53
  209. package/lib/canId.js +0 -64
  210. package/lib/canbus.js +0 -278
  211. package/lib/codes.js +0 -69
  212. package/lib/codes.test.js +0 -17
  213. package/lib/codesMfgs.json +0 -166
  214. package/lib/fromPgn.js +0 -1027
  215. package/lib/n2k-actisense.js +0 -108
  216. package/lib/n2kDevice.js +0 -390
  217. package/lib/pgns.js +0 -196
  218. package/lib/pgns.test.js +0 -13
  219. package/lib/serial.js +0 -647
  220. package/lib/simpleCan.js +0 -105
  221. package/lib/stringMsg.js +0 -339
  222. package/lib/toPgn.js +0 -518
@@ -0,0 +1,458 @@
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 { createDebug } from './utilities'
36
+ import { EventEmitter } from 'node:events'
37
+ import _ from 'lodash'
38
+ import { Uint64LE } from 'int64-buffer'
39
+ import { defaultTransmitPGNs } from './codes'
40
+ import { toPgn } from './toPgn'
41
+ import packageJson from '../package.json'
42
+
43
+ const debug = createDebug('canboatjs:n2kdevice')
44
+
45
+ const deviceTransmitPGNs = [60928, 59904, 126996, 126464]
46
+
47
+ export class N2kDevice extends EventEmitter {
48
+ addressClaim: any
49
+ productInfo: any
50
+ configurationInfo: any
51
+ options: any
52
+ address: number
53
+ cansend: boolean
54
+ foundConflict: boolean
55
+ heartbeatCounter: number
56
+ devices: any
57
+ sentAvailable: boolean
58
+ addressClaimDetectionTime: number
59
+ transmitPGNs: number[]
60
+ addressClaimSentAt?: number
61
+ addressClaimChecker?: any
62
+ heartbeatInterval?: any
63
+
64
+ constructor(options: any) {
65
+ super()
66
+
67
+ if (options.addressClaim) {
68
+ this.addressClaim = options.addressClaim
69
+ this.addressClaim.pgn = 60928
70
+ this.addressClaim.dst = 255
71
+ this.addressClaim.prio = 6
72
+ } else {
73
+ this.addressClaim = {
74
+ pgn: 60928,
75
+ dst: 255,
76
+ prio: 6,
77
+ 'Unique Number': 1263,
78
+ 'Manufacturer Code': 999,
79
+ 'Device Function': 130, // PC gateway
80
+ 'Device Class': 25, // Inter/Intranetwork Device
81
+ 'Device Instance Lower': 0,
82
+ 'Device Instance Upper': 0,
83
+ 'System Instance': 0,
84
+ 'Industry Group': 4, // Marine
85
+ Reserved1: 1,
86
+ Reserved2: 2
87
+ }
88
+ this.addressClaim['Unique Number'] =
89
+ options.uniqueNumber || Math.floor(Math.random() * Math.floor(2097151))
90
+ }
91
+
92
+ const version = packageJson ? packageJson.version : '1.0'
93
+
94
+ if (options.productInfo) {
95
+ this.productInfo = options.productInfo
96
+ this.productInfo.pgn = 126996
97
+ this.productInfo.dst = 255
98
+ } else {
99
+ this.productInfo = {
100
+ pgn: 126996,
101
+ dst: 255,
102
+ 'NMEA 2000 Version': 1300,
103
+ 'Product Code': 667, // Just made up..
104
+ 'Model ID': 'Signal K',
105
+ 'Model Version': 'canboatjs',
106
+ 'Model Serial Code': options.uniqueNumber
107
+ ? options.uniqueNumber.toString()
108
+ : '000001',
109
+ 'Certification Level': 0,
110
+ 'Load Equivalency': 1
111
+ }
112
+ }
113
+
114
+ this.productInfo['Software Version Code'] = version
115
+
116
+ if (options.serverVersion && options.serverUrl) {
117
+ this.configurationInfo = {
118
+ pgn: 126998,
119
+ dst: 255,
120
+ 'Installation Description #1': options.serverUrl,
121
+ 'Installation Description #2': options.serverDescription,
122
+ 'Manufacturer Information': options.serverVersion
123
+ }
124
+ }
125
+
126
+ this.options = _.isUndefined(options) ? {} : options
127
+
128
+ this.address = _.isUndefined(options.preferredAddress)
129
+ ? 100
130
+ : options.preferredAddress
131
+ this.cansend = false
132
+ this.foundConflict = false
133
+ this.heartbeatCounter = 0
134
+ this.devices = {}
135
+ this.sentAvailable = false
136
+ this.addressClaimDetectionTime =
137
+ options.addressClaimDetectionTime !== undefined
138
+ ? options.addressClaimDetectionTime
139
+ : 5000
140
+
141
+ if (!options.disableDefaultTransmitPGNs) {
142
+ this.transmitPGNs = _.union(deviceTransmitPGNs, defaultTransmitPGNs)
143
+ } else {
144
+ this.transmitPGNs = [...deviceTransmitPGNs]
145
+ }
146
+
147
+ if (this.options.transmitPGNs) {
148
+ this.transmitPGNs = _.union(this.transmitPGNs, this.options.transmitPGNs)
149
+ }
150
+ }
151
+
152
+ start() {
153
+ sendISORequest(this, 60928, 254)
154
+ setTimeout(() => {
155
+ sendAddressClaim(this)
156
+ }, 1000)
157
+ }
158
+
159
+ setStatus(msg: string) {
160
+ if (this.options.app && this.options.app.setPluginStatus) {
161
+ this.options.app.setProviderStatus(this.options.providerId, msg)
162
+ }
163
+ }
164
+
165
+ n2kMessage(pgn: PGN) {
166
+ if (pgn.dst == 255 || pgn.dst == this.address) {
167
+ try {
168
+ if (pgn.pgn == 59904) {
169
+ handleISORequest(this, pgn)
170
+ } else if (pgn.pgn == 126208) {
171
+ handleGroupFunction(this, pgn as PGN_126208)
172
+ } else if (pgn.pgn == 60928) {
173
+ handleISOAddressClaim(this, pgn as PGN_60928)
174
+ } else if (pgn.pgn == 126996) {
175
+ handleProductInformation(this, pgn)
176
+ }
177
+ } catch (err) {
178
+ console.error(err)
179
+ }
180
+
181
+ /*
182
+ var handler = this.handlers[pgn.pgn.toString()]
183
+ if ( pgn.dst == this.address )
184
+ debug(`handler ${handler}`)
185
+ if ( _.isFunction(handler) ) {
186
+ debug(`got handled PGN %j ${handled}`, pgn)
187
+ handler(pgn)
188
+ }
189
+ */
190
+ }
191
+ }
192
+
193
+ sendPGN(_pgn: PGN, _src: number | undefined = undefined) {}
194
+ }
195
+
196
+ function handleISORequest(device: N2kDevice, n2kMsg: PGN_59904) {
197
+ debug('handleISORequest %j', n2kMsg)
198
+
199
+ const PGN = Number(n2kMsg.fields.pgn)
200
+
201
+ switch (PGN) {
202
+ case 126996: // Product Information request
203
+ sendProductInformation(device)
204
+ break
205
+ case 126998: // Config Information request
206
+ sendConfigInformation(device)
207
+ break
208
+ case 60928: // ISO address claim request
209
+ debug('sending address claim %j', device.addressClaim)
210
+ device.sendPGN(device.addressClaim as PGN)
211
+ break
212
+ case 126464:
213
+ sendPGNList(device, n2kMsg.src!)
214
+ break
215
+ default:
216
+ if (!device.options.disableNAKs) {
217
+ debug(`Got unsupported ISO request for PGN ${PGN}. Sending NAK.`)
218
+ sendNAKAcknowledgement(device, n2kMsg.src!, PGN)
219
+ }
220
+ }
221
+ }
222
+
223
+ function handleGroupFunction(device: N2kDevice, n2kMsg: PGN_126208) {
224
+ debug('handleGroupFunction %j', n2kMsg)
225
+ const functionCode = n2kMsg.fields.functionCode
226
+ if (functionCode === 'Request') {
227
+ handleRequestGroupFunction(device, n2kMsg)
228
+ } else if (functionCode === 'Command') {
229
+ handleCommandGroupFunction(device, n2kMsg)
230
+ } else {
231
+ debug('Got unsupported Group Function PGN: %j', n2kMsg)
232
+ }
233
+
234
+ function handleRequestGroupFunction(device: N2kDevice, n2kMsg: PGN_126208) {
235
+ if (!device.options.disableNAKs) {
236
+ // We really don't support group function requests for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
237
+
238
+ const PGN = n2kMsg.fields.pgn
239
+
240
+ debug(
241
+ "Sending 'PGN Not Supported' Group Function response for requested PGN",
242
+ PGN
243
+ )
244
+
245
+ const acknowledgement: PGN_126208_Acknowledge = {
246
+ pgn: 126208,
247
+ dst: n2kMsg.src!,
248
+ fields: {
249
+ functionCode: GroupFunction.Acknowledge,
250
+ pgn: PGN,
251
+ pgnErrorCode: PgnErrorCode.NotSupported,
252
+ transmissionIntervalPriorityErrorCode:
253
+ TransmissionInterval.Acknowledge,
254
+ numberOfParameters: 0,
255
+ list: []
256
+ }
257
+ }
258
+ device.sendPGN(acknowledgement)
259
+ }
260
+ }
261
+
262
+ function handleCommandGroupFunction(
263
+ device: N2kDevice,
264
+ n2kMsg: PGN_126208_Command
265
+ ) {
266
+ if (!device.options.disableNAKs) {
267
+ // We really don't support group function commands for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
268
+
269
+ const PGN = n2kMsg.fields.pgn
270
+
271
+ debug(
272
+ "Sending 'PGN Not Supported' Group Function response for commanded PGN",
273
+ PGN
274
+ )
275
+
276
+ const acknowledgement: PGN_126208_Acknowledge = {
277
+ pgn: 126208,
278
+ dst: n2kMsg.src!,
279
+ fields: {
280
+ functionCode: GroupFunction.Acknowledge,
281
+ pgn: PGN,
282
+ pgnErrorCode: PgnErrorCode.NotSupported,
283
+ transmissionIntervalPriorityErrorCode:
284
+ TransmissionInterval.Acknowledge,
285
+ numberOfParameters: 0,
286
+ list: []
287
+ }
288
+ }
289
+ device.sendPGN(acknowledgement)
290
+ }
291
+ }
292
+ }
293
+
294
+ function handleISOAddressClaim(device: N2kDevice, n2kMsg: PGN_60928) {
295
+ if (n2kMsg.src != device.address) {
296
+ if (!device.devices[n2kMsg.src!]) {
297
+ debug(`registering device ${n2kMsg.src}`)
298
+ device.devices[n2kMsg.src!] = { addressClaim: n2kMsg }
299
+ if (device.cansend) {
300
+ //sendISORequest(device, 126996, undefined, n2kMsg.src)
301
+ }
302
+ }
303
+ return
304
+ }
305
+
306
+ debug('Checking ISO address claim. %j', n2kMsg)
307
+
308
+ const uint64ValueFromReceivedClaim = getISOAddressClaimAsUint64(n2kMsg)
309
+ const uint64ValueFromOurOwnClaim = getISOAddressClaimAsUint64(
310
+ device.addressClaim
311
+ )
312
+
313
+ if (uint64ValueFromOurOwnClaim < uint64ValueFromReceivedClaim) {
314
+ debug(`Address conflict detected! Kept our address as ${device.address}.`)
315
+ sendAddressClaim(device) // We have smaller address claim data -> we can keep our address -> re-claim it
316
+ } else if (uint64ValueFromOurOwnClaim > uint64ValueFromReceivedClaim) {
317
+ device.foundConflict = true
318
+ increaseOwnAddress(device) // We have bigger address claim data -> we have to change our address
319
+ debug(`Address conflict detected! trying address ${device.address}.`)
320
+ sendAddressClaim(device)
321
+ }
322
+ }
323
+
324
+ function increaseOwnAddress(device: N2kDevice) {
325
+ const start = device.address
326
+ do {
327
+ device.address = (device.address + 1) % 253
328
+ } while (device.address != start && device.devices[device.address])
329
+ }
330
+
331
+ function handleProductInformation(device: N2kDevice, n2kMsg: PGN_126996) {
332
+ if (!device.devices[n2kMsg.src!]) {
333
+ device.devices[n2kMsg.src!] = {}
334
+ }
335
+ debug('got product information %j', n2kMsg)
336
+ device.devices[n2kMsg.src!].productInformation = n2kMsg
337
+ }
338
+
339
+ function sendHeartbeat(device: N2kDevice) {
340
+ device.heartbeatCounter = device.heartbeatCounter + 1
341
+ if (device.heartbeatCounter > 252) {
342
+ device.heartbeatCounter = 0
343
+ }
344
+
345
+ const hb: PGN_126993 = {
346
+ pgn: 126993,
347
+ dst: 255,
348
+ prio: 7,
349
+ fields: {
350
+ dataTransmitOffset: 60,
351
+ sequenceCounter: device.heartbeatCounter,
352
+ controller1State: ControllerState.ErrorActive
353
+ }
354
+ }
355
+
356
+ device.sendPGN(hb)
357
+ }
358
+
359
+ function sendAddressClaim(device: N2kDevice) {
360
+ if (device.devices[device.address]) {
361
+ //someone already has this address, so find a free one
362
+ increaseOwnAddress(device)
363
+ }
364
+ debug(`Sending address claim ${device.address}`)
365
+ device.sendPGN(device.addressClaim)
366
+ device.setStatus(`Claimed address ${device.address}`)
367
+ device.addressClaimSentAt = Date.now()
368
+ if (device.addressClaimChecker) {
369
+ clearTimeout(device.addressClaimChecker)
370
+ }
371
+
372
+ device.addressClaimChecker = setTimeout(() => {
373
+ //if ( Date.now() - device.addressClaimSentAt > 1000 ) {
374
+ //device.addressClaimChecker = null
375
+ debug('claimed address %d', device.address)
376
+ device.cansend = true
377
+ if (!device.sentAvailable) {
378
+ if (device.options.app) {
379
+ device.options.app.emit('nmea2000OutAvailable')
380
+ }
381
+ device.emit('nmea2000OutAvailable')
382
+ device.sentAvailable = true
383
+ }
384
+ sendISORequest(device, 126996)
385
+ if (!device.heartbeatInterval) {
386
+ device.heartbeatInterval = setInterval(() => {
387
+ sendHeartbeat(device)
388
+ }, 60 * 1000)
389
+ }
390
+ //}
391
+ }, device.addressClaimDetectionTime)
392
+ }
393
+
394
+ function sendISORequest(
395
+ device: N2kDevice,
396
+ pgn: number,
397
+ src: number | undefined = undefined,
398
+ dst = 255
399
+ ) {
400
+ debug(`Sending iso request for ${pgn} to ${dst}`)
401
+
402
+ const isoRequest: PGN_59904 = {
403
+ pgn: 59904,
404
+ dst: dst,
405
+ fields: {
406
+ pgn
407
+ }
408
+ }
409
+ device.sendPGN(isoRequest, src)
410
+ }
411
+
412
+ function sendProductInformation(device: N2kDevice) {
413
+ debug('Sending product info %j', device.productInfo)
414
+
415
+ device.sendPGN(device.productInfo)
416
+ }
417
+
418
+ function sendConfigInformation(device: N2kDevice) {
419
+ if (device.configurationInfo) {
420
+ debug('Sending config info..')
421
+ device.sendPGN(device.configurationInfo)
422
+ }
423
+ }
424
+
425
+ function sendNAKAcknowledgement(
426
+ device: N2kDevice,
427
+ src: number,
428
+ requestedPGN: number
429
+ ) {
430
+ const acknowledgement: PGN_59392 = {
431
+ pgn: 59392,
432
+ dst: src,
433
+ fields: {
434
+ control: IsoControl.Ack,
435
+ groupFunction: 255,
436
+ pgn: requestedPGN
437
+ }
438
+ }
439
+ device.sendPGN(acknowledgement)
440
+ }
441
+
442
+ function sendPGNList(device: N2kDevice, dst: number) {
443
+ //FIXME: for now, adding everything that signalk-to-nmea2000 supports
444
+ //need a way for plugins, etc. to register the pgns they provide
445
+ const pgnList: PGN_126464 = {
446
+ pgn: 126464,
447
+ dst,
448
+ fields: {
449
+ functionCode: PgnListFunction.TransmitPgnList,
450
+ list: device.transmitPGNs
451
+ }
452
+ }
453
+ device.sendPGN(pgnList)
454
+ }
455
+
456
+ function getISOAddressClaimAsUint64(pgn: any) {
457
+ return new Uint64LE(toPgn(pgn)!)
458
+ }
@@ -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
+ }