-
Notifications
You must be signed in to change notification settings - Fork 0
/
libserialport-io.lisp
373 lines (306 loc) · 12.5 KB
/
libserialport-io.lisp
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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
;; functions to read/write
(in-package libserialport)
(defun serial-write-data (serial-port data
&key
(encoding :latin-1) ;; for string only
(blocking nil)
(timeout 1000)
(line-end nil))
"Write DATA to SERIAL-PORT. DATA can be a string, unsigned byte8 vector, a
number from 0-255, or a character. Unicode data are translated to octets
using babel package, using ENCODING.
The number of octets written is returned.
BLOCKING indicates whether blocking output should be used.
TIMEOUT is the timeout in milliseconds if BLOCKING is true. LINE-END
is a line termination to append; it can be NIL (none),:CR,:LF,:CRLF"
(declare (type serial-port serial-port)
(type (member nil :cr :lf :crlf) line-end)
(type (unsigned-byte 24) timeout)
(type (or string
(unsigned-byte 8)
character
(simple-array (unsigned-byte 8) (*)))
data))
(assert (serial-port-alive-p serial-port))
(let* ((bytes ;; a byte array, if we're writing a sequence
(cond
((stringp data)
(babel:string-to-octets data :encoding encoding))
((typep data '(simple-array (unsigned-byte 8) (*))) ;; unsigned byte 8
data)
;; out of range char so turn into encoded string
((and (characterp data)
(> (char-code data) 255))
(babel:string-to-octets
(make-string 1 :initial-element data)
:encoding encoding))
(t ;; an integer or char<255
nil)))
;; just one byte
(one-byte (if (not bytes)
(if (integerp data)
data
(char-code data))))
;; number of bytes for line-end
(nend (cond ((not line-end) 0)
((eq line-end :crlf) 2)
(t 1))) ;; :CR or :LF
(nbytes
(+ nend
(cond (bytes (length bytes))
(t 1))))) ;; a single char or ingeter
;; set up a foreign BUF with one more data bytes
(with-serial-port-buffer (buf serial-port nbytes)
(let ((nlast 0))
(cond
(bytes ;; case of a sequence of data
(loop for i of-type fixnum below (length bytes)
do
(incf nlast)
(setf (cffi:mem-ref buf :unsigned-char i) (aref bytes i))))
;;
(one-byte ;; else a single dataum [0-255]
(incf nlast)
(setf (cffi:mem-ref buf :unsigned-char 0) one-byte)))
;; add a line termination if requested
(cond ((eq line-end :cr)
(setf (cffi:mem-ref buf :unsigned-char nlast) #.(char-code #\cr)))
((eq line-end :lf)
(setf (cffi:mem-ref buf :unsigned-char nlast) #.(char-code #\lf)))
((eq line-end :crlf)
(setf (cffi:mem-ref buf :unsigned-char nlast) #.(char-code #\cr))
(incf nlast)
(setf (cffi:mem-ref buf :unsigned-char nlast) #.(char-code #\lf)))))
;;
(cond
(blocking
(let ((retval
(sp-blocking-write (serial-port-port-ptr serial-port) buf nbytes timeout)))
(when (symbolp retval) ;; an error code
(error "ERROR ~A blocking writing bytes to serial port." retval))
retval)) ;; number of bytes written
;;
((not blocking)
(let ((retval (sp-nonblocking-write (serial-port-port-ptr serial-port) buf nbytes)))
(when (symbolp retval) ;; an error code
(error "ERROR ~A non-blocking writing bytes to serial port." retval))
retval)))))) ;; number of bytes written
(defun serial-read-octets (serial-port count
&key
(blocking nil)
(timeout 1000)
(octet-buf nil)
(append nil))
"Read at most COUNT octets from SERIAL-PORT.
BLOCKING indicates blocking or no-blocking output.
TIMEOUT is the timeout in milliseconds for blocking mode (0 means no timeout)
OCTET-BUF is an optional output array. If given, it must be an adjustable
(unsigned-byte 8) array with a fill pointer.
APPEND means to append the new output to the end of OCTET-BUF at the
fill pointer, instead of starting with an empty vector.
The special value of COUNT=-1 means to return a single octet or NIL, instead
of a vector.
Returns (VALUES OCTETS NUM-OCTETS-READ)."
(declare (type serial-port serial-port)
(type (or (member -1) unsigned-byte) count)
(type (or null (array (unsigned-byte 8) (*))) octet-buf))
(when (zerop count) (error "Cannot read zero octets."))
(when octet-buf
(when (not (and (adjustable-array-p octet-buf)
(array-has-fill-pointer-p octet-buf)))
(error "OCTET-BUF is not a (unsigned-byte 8) array that is adjustable and has a fill pointer."))
(when (not append)
(setf (fill-pointer octet-buf) 0)))
;; allocate foreign and Lisp storage, trying to use buffers inside
;; serial-port
(with-serial-port-buffer (fbuf serial-port count)
(block retblock
(let ((acount (abs count))
(nbytes-read nil))
(cond (blocking
(let ((retval
(sp-blocking-read
(serial-port-port-ptr serial-port)
fbuf acount timeout)))
(when (and (symbolp retval) ;; an error code
(not (eq retval :sp-ok)))
(error "ERROR ~A blocking readingbytes to serial port."
retval))
(setf nbytes-read retval)))
((not blocking)
(let ((retval
(sp-nonblocking-read
(serial-port-port-ptr serial-port)
fbuf acount)))
(when (and (symbolp retval) ;; an error code
(not (eq retval :sp-ok)))
(error "ERROR ~A non-blocking reading bytes to serial port."
retval))
(setf nbytes-read retval))))
;; zero-byte successful read
(if (eq nbytes-read :sp-ok)
(setf nbytes-read 0))
(if (= count -1)
;; special case of count=1 ==> (values nil-or-char nbytes-read)
(return-from retblock
(values
(if (plusp nbytes-read) (cffi:mem-ref fbuf :unsigned-char 0) nil)
nbytes-read))
;; else grow the actual output buf
(let ((obuf
(or
;; use the passed buffer if given
octet-buf
;; otherwise allocate a new vector
(make-array 0 :element-type '(unsigned-byte 8)
:adjustable t
:fill-pointer 0))))
(loop for i of-type fixnum below nbytes-read
do (vector-push-extend
(cffi:mem-ref fbuf :unsigned-char i)
obuf))
(return-from retblock (values obuf nbytes-read))))))))
(defun serial-read-octet (serial-port &key (blocking nil) (timeout 1000))
"Read one octet from SERIAL-PORT, or NIL if none."
(multiple-value-bind (octet nbytes)
(serial-read-octets serial-port -1 :blocking blocking :timeout timeout)
(values octet nbytes)))
(defun serial-read-char8 (serial-port &key (blocking nil) (timeout 1000))
"Read one char (with char-code < 255) from SERIAL-PORT, or NIL if none."
(let ((byte (serial-read-octet serial-port :blocking blocking :timeout timeout)))
(when byte (code-char byte))))
(defun serial-input-waiting (serial-port)
"Returns the number of input octets waiting in the SERIAL-PORT.
Can also return a non-negtive number or a keyword indicating an error."
(assert (serial-port-alive-p serial-port))
(let ((ret (sp-input-waiting (serial-port-port-ptr serial-port))))
(when (eq ret :sp-ok) (setf ret 0))
(when (symbolp ret)
(error "ERROR ~A in serial-input-waiting." ret))
ret))
(defun serial-output-waiting (serial-port)
"Returns the number of input octets waiting in the SERIAL-PORT.
Can also return a non-negtive number or a keyword indicating an error."
(assert (serial-port-alive-p serial-port))
(let ((ret (sp-output-waiting (serial-port-port-ptr serial-port))))
(when (eq ret :sp-ok) (setf ret 0))
(when (symbolp ret)
(error "ERROR ~A in serial-output-waiting." ret))
ret))
(defun serial-read-octets-until (serial-port final-octet
&key
(blocking nil)
(timeout 1000)
(max-length 65536)
(octet-buf nil)
(append nil))
"Read a vector of octets from SERIAL-PORT until FINAL-OCTET is read,
returning an octet array, without the final octet. MAX-LENGTH is the
maximum permitted length.
Returns (VALUES OCTET-VECTOR FINAL-OCTET-READ TIMED-OUT-P). If
FINAL-OCTET-READ is not FINAL-OCTET the it will be NIL, which probably
means the operation timed out. TIMED-OUT-P is true if a timeout occurred
in the char-by-char loop to read octets.
The TIMEOUT in milliseconds is both the individual octet read
timeout (if BLOCKING), and a total TIMEOUT to accumulate bytes.
Note that the presence of TIMEOUT in the internal character gathering
loop means that the call is effectively blocking, even if BLOCKING is NIL.
(TIMEOUT=0 still means infinite timeout).
OCTET-BUF is an optional output array. If given, it must be an adjustable
(unsigned-byte 8) array with a fill pointer.
If APPEND is true, then it will continue to append to OCTET-BUF; otherwise
OCTET-BUF is reset to start."
(cond
(octet-buf
(when (not (and (adjustable-array-p octet-buf)
(typep octet-buf '(array (unsigned-byte 8) (*)))
(array-has-fill-pointer-p octet-buf)))
(error "OCTET-BUF is not a (unsigned-byte 8) array that is adjutable and has a fill pointer."))
(when (not append) (setf (fill-pointer octet-buf) 0)))
(t ;; no octet-buf
(when append
(error "ERROR in SERIAL-READ-OCTETS-UNTIL - APPEND cannot be used unless OCTET-BUF is supplied"))))
(let* ((ovec (or octet-buf
(make-array 32 :element-type '(unsigned-byte 8)
:adjustable t :fill-pointer 0)))
(clock-ticks-timeout ;; internal clock ticks
(round (* internal-time-units-per-second (/ timeout 1000.0))))
(end-time (+ (get-internal-real-time) clock-ticks-timeout)))
(declare (type fixnum clock-ticks-timeout end-time))
(loop with nbytes-read = 0
for timed-out-p = (and (plusp timeout) ;; zero timeout means infinite
(> (get-internal-real-time) end-time))
for data-ready = (serial-input-waiting serial-port)
if (and (not timed-out-p)
(not (plusp data-ready)))
do
(sleep 1e-4)
else
do
(let ((byte (serial-read-octet serial-port
:blocking blocking :timeout timeout)))
(cond ((or timed-out-p
(eql byte final-octet)
(eql byte nil))
(return (values ovec byte timed-out-p)))
(t
(incf nbytes-read)
(when (> nbytes-read max-length)
(error "Number of bytes read exceeds MAX-LENGTH=~A" max-length))
(vector-push-extend byte ovec)))))))
(defun serial-read-line (serial-port
&key
(blocking nil)
(timeout 1000)
(max-length 65536)
(line-termination-char #\lf)
(ignore-final-carriage-return t)
(encoding :latin-1))
"Read a line from SERIAL-PORT. MAX-LENGTH is the maxinum number of
octets (not chars), and the line ends with LINE-TERMINATION-CHAR (by
default #\linefeed), and by default a final #\Return char is stripped
to allow DOS lines to be read.
The TIMEOUT is both the individual octet read timeout (if BLOCKING), and
a total TIMEOUT to accumulate bytes.
Note that the presence of TIMEOUT in the internal character gathering
loop means that the call is effectively blocking, even if BLOCKING is NIL.
(TIMEOUT=0 still means infinite timeout).
Returns (VALUES STRING FINISHED-LINE-P DECODING-ERROR OCTETS TIMED-OUT-P)
where FINISHED-LINE-P is true if LINE-TERMINATION-CHAR was reached;
otherwise there may have been a timeout. STRING can be NIL if there
is an error in Babel decoding the string using the encoding given, in
which case DECODING-ERROR is non-NIL. OCTETS is the vector of raw
octets, possibly minus the #\Return."
(multiple-value-bind (ovec final-byte timed-out-p)
(serial-read-octets-until serial-port (char-code line-termination-char)
:blocking blocking
:timeout timeout
:max-length max-length)
;; get rid of final #\Return
(when (and ignore-final-carriage-return
(plusp (length ovec))
(= (aref ovec (1- (length ovec))) #.(char-code #\cr)))
(vector-pop ovec))
;;
(multiple-value-bind (string decoding-error)
(ignore-errors (babel:octets-to-string ovec :encoding encoding))
(values string
(eql final-byte (char-code line-termination-char))
decoding-error
ovec
timed-out-p))))
(defun serial-flush-buffer (serial-port &key (flush-input-buffer t) (flush-output-buffer t))
"Flush serial port buffers, discarding data."
(assert (serial-port-alive-p serial-port))
(when (or flush-input-buffer flush-output-buffer)
(sp-flush (serial-port-port-ptr serial-port)
(cond ((and flush-input-buffer flush-output-buffer)
:sp-buf-both)
(flush-input-buffer
:sp-buf-input)
(flush-output-buffer
:sp-buf-output)))))
(defun serial-drain-buffer (serial-port)
"Wait until output of SERIAL-PORT have been transmited - no timeout option"
(assert (serial-port-alive-p serial-port))
(sp-drain (serial-port-port-ptr serial-port)))