-
Notifications
You must be signed in to change notification settings - Fork 48
/
Copy pathSender.js
277 lines (227 loc) · 6.59 KB
/
Sender.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
const SerialPort = require('serialport')
const Readline = require('@serialport/parser-readline')
const fs = require('fs')
const EventEmitter = require('events')
class MyEmitter extends EventEmitter {}
// Constants for events
const START = 'start'
const CHUNK_SUCCESS = 'chunkSuccess'
const CHUNK_FAILURE = 'chunkFailure'
const EOF = 'eof'
// Constants for serial port communication
const SERIAL_ERROR = 'error'
const SERIAL_DATA = 'data'
// Protocol constants
const PROTOCOL_OK = 'k'
const PROTOCOL_FAILURE = 'f'
// JSON config file for all connection related details
const CONFIG_FILE_NAME = '.sender_config.json'
// Application timeout values
const startTimeout = 2000
const byteTimeout = 0 // not needed, data transfer is stable w/o positive values
const chunkTimeout = 0 // not needed, since we wait for Arduino ack after every chunk anyways
// get a serial connection with default params
const connectSerial = (config, tty) => {
const connection = new SerialPort(tty || config.tty, {
baudRate: config.baudrate,
databits: config.databits,
parity: config.parity,
stopbits: config.stopbits
})
// Open errors will be emitted as an error event
connection.on(SERIAL_ERROR, (err) => {
console.log('Error on read: ', err.message)
})
return connection
}
// establish a parser for data read from Arduino
const establishParser = (connection, emitter) => {
const parser = new Readline()
connection.pipe(parser)
parser.on(SERIAL_DATA, (data) => {
const response = data.toString().trim()
if (response == PROTOCOL_OK) {
emitter.emit(CHUNK_SUCCESS)
} else if (response == PROTOCOL_FAILURE) {
emitter.emit(CHUNK_FAILURE)
} else {
console.log('Arduino Response: ', response)
}
})
}
// write a single byte to the serial connection
const sendByte = (connection, char) => {
connection.write(char, (err) => {
if (err) {
return console.log('Error on write: ', err.message)
}
})
}
// write a chunk of bytes to the serial connection including checksum
const sendChunk = (connection, data) => {
let idx = 0
// both methods are destructive
data = appendPadding(data)
data = appendChecksum(data)
base64 = data.toString('base64')
// let decimals = data.join('-')
// console.log('Data: ', decimals)
// console.log('Base64: ', base64)
return new Promise((res) => {
setTimeout(() => {
const interval = setInterval(() => {
if (idx == base64.length) {
clearInterval(interval)
res()
} else {
sendByte(connection, base64[idx])
idx += 1
}
}, byteTimeout)
}, chunkTimeout)
})
}
// simple 1-byte checksum algorithm
const checkSum = (data) => {
let cs = 0
data.forEach((element) => {
bin = element.toString(2)
cs = (cs << 1) + parseInt(bin[bin.length - 1])
})
return cs
}
// appends checksum to given buffer
const appendChecksum = (buf) => {
return Buffer.concat([buf, Buffer.alloc(1, [checkSum(buf)])], 9)
}
// add 0x00 padding bytes to buffer of different length than 8 (potentially only the last)
// necessary, because otherwise the checksum will not be in the right place and
// the chunk will be requested forever
const appendPadding = (buf) => {
if (buf.length == 8) {
return buf
} else {
return Buffer.concat([buf, Buffer.alloc((8 - buf.length), 0)], 8)
}
}
// reads a file from filesystem and async returns it as a buffer
const readFile = (fileName) => {
return new Promise((res, rej) => {
fs.readFile(fileName, (err, data) => {
if (err) { rej('Error while reading file: ' + fileName) }
res(data)
})
})
}
// cut the given array / buffer into chunks of given size
const inGroupsOf = (ary, size) => {
let result = []
for (let i = 0; i < ary.length; i += size) {
let chunk = ary.slice(i, i + size)
result.push(chunk)
}
return result
}
// simple index constructor function
const Index = (m) => {
let idx = 0
const max = m - 1
const get = () => {
return idx
}
const increase = () => {
if (idx >= max) {
return null
} else {
idx += 1
return idx
}
}
return {
get,
increase
}
}
// registering all event handler functions
const establishEventHandlers = (connection, index, chunks) => {
const myEmitter = new MyEmitter()
myEmitter.on(START, (connection, chunk) => {
sendFirstChunk(connection, chunk)
})
myEmitter.on(CHUNK_SUCCESS, () => {
sendNextChunk(connection, index, chunks, myEmitter)
})
myEmitter.on(CHUNK_FAILURE, () => {
repeatChunk(connection, index, chunks, myEmitter)
})
myEmitter.on(EOF, () => {
quit(connection)
})
return myEmitter
}
// sends the very first chunk
const sendFirstChunk = (connection, chunk) => {
console.log('Sending chunk: 0')
sendChunk(connection, chunk)
}
// increases index and sends next chunk
// emits EOF event, when no chunk is left
const sendNextChunk = (connection, index, chunks, emitter) => {
const idx = index.increase()
if (idx) {
console.log('Sending chunk: ', idx)
sendChunk(connection, chunks[idx])
} else {
console.log('No chunk left!')
emitter.emit(EOF)
}
}
// gets current index and repeats the chunk sent before
const repeatChunk = (connection, index, chunks, emitter) => {
const idx = index.get()
if (idx) {
console.log('Repeating chunk: ', idx)
sendChunk(connection, chunks[idx])
} else {
emitter.emit(EOF)
}
}
// gracefully quits the program
const quit = (connection) => {
console.log('Data transferred successfully! Quitting.')
connection.close()
process.exit(0)
}
// main
(async () => {
const inFileName = process.argv[2]
const tty = process.argv[3]
if (!inFileName) {
console.log('No input file given!')
process.exit(1)
}
try {
console.log('Reading input file: ', inFileName)
const inFile = await readFile(inFileName)
console.log('Done.')
const chunks = inGroupsOf(inFile, 8)
console.log('Loading config file...')
const config = JSON.parse(fs.readFileSync(CONFIG_FILE_NAME))
console.log('Establishing connection...')
const connection = connectSerial(config, tty)
const index = Index(chunks.length)
console.log('Establishing event handlers...')
emitter = establishEventHandlers(connection, index, chunks)
console.log('Done.')
setTimeout(() => {
console.log('Connected.')
console.log('Establishing parser...')
establishParser(connection, emitter)
console.log('Ready to send data.')
emitter.emit(START, connection, chunks[0])
}, startTimeout)
} catch(e) {
console.log(e)
process.exit(1)
}
})()