@@ -19,7 +19,7 @@ export interface SerialConnectionResult {
1919
2020// Known NFC reader USB vendor/product IDs for serial filter
2121export const KNOWN_SERIAL_NFC_READERS = [
22- { usbVendorId : 0x1fd3 , usbProductId : 0x0108 } , // RDR-518 NFC Reader
22+ { usbVendorId : 0x1fd3 , usbProductId : 0x0108 } , // ASK RDR-518
2323 { usbVendorId : 0x072f } , // ACS readers
2424 { usbVendorId : 0x1a86 } , // CH340
2525 { usbVendorId : 0x0403 } , // FTDI
@@ -127,82 +127,135 @@ export async function sendSerialData(device: SerialDevice, data: Uint8Array): Pr
127127}
128128
129129/**
130- * Receive data from serial port with timeout
130+ * Send command and receive response
131+ * For ASK RDR-518: response format is [LEN] [DATA...] [CRC-16]
131132 */
132- export async function receiveSerialData (
133+ export async function transceiveSerial (
133134 device : SerialDevice ,
135+ command : Uint8Array ,
134136 timeout : number = 2000
137+ ) : Promise < Uint8Array > {
138+ if ( ! device . reader || ! device . writer ) {
139+ throw new Error ( 'Serial port not ready' ) ;
140+ }
141+
142+ // Clear any stale data in the buffer first
143+ await clearSerialBuffer ( device ) ;
144+
145+ // Send the command
146+ await sendSerialData ( device , command ) ;
147+
148+ // Wait for device to process command and perform RF operation
149+ await new Promise ( resolve => setTimeout ( resolve , 150 ) ) ;
150+
151+ // Read response, looking for a complete frame
152+ return await receiveSerialFrame ( device , timeout ) ;
153+ }
154+
155+ /**
156+ * Clear any stale data from the serial buffer
157+ */
158+ async function clearSerialBuffer ( device : SerialDevice ) : Promise < void > {
159+ if ( ! device . reader ) return ;
160+
161+ const startTime = Date . now ( ) ;
162+ while ( Date . now ( ) - startTime < 100 ) {
163+ try {
164+ const readPromise = device . reader . read ( ) ;
165+ const timeoutPromise = new Promise < { value : undefined ; done : true } > ( ( resolve ) =>
166+ setTimeout ( ( ) => resolve ( { value : undefined , done : true } ) , 20 )
167+ ) ;
168+ const result = await Promise . race ( [ readPromise , timeoutPromise ] ) ;
169+ if ( result . done || ! result . value || result . value . length === 0 ) {
170+ break ; // Buffer is empty
171+ }
172+ // Discard stale data and continue clearing
173+ } catch {
174+ break ;
175+ }
176+ }
177+ }
178+
179+ /**
180+ * Receive a complete frame from serial port
181+ * Frame format: [LEN] [DATA...] [CRC-16]
182+ * LEN=0x04 for no-card (8 bytes total), LEN=0x0b for card-found (15 bytes total)
183+ */
184+ async function receiveSerialFrame (
185+ device : SerialDevice ,
186+ timeout : number
135187) : Promise < Uint8Array > {
136188 if ( ! device . reader ) {
137189 throw new Error ( 'Serial port reader not available' ) ;
138190 }
139191
140192 const chunks : Uint8Array [ ] = [ ] ;
141193 const startTime = Date . now ( ) ;
194+ let totalBytes = 0 ;
142195
143- try {
144- while ( Date . now ( ) - startTime < timeout ) {
145- const readPromise = device . reader . read ( ) ;
146- const timeoutPromise = new Promise < { value : undefined ; done : true } > ( ( resolve ) =>
147- setTimeout ( ( ) => resolve ( { value : undefined , done : true } ) , Math . max ( 100 , timeout - ( Date . now ( ) - startTime ) ) )
148- ) ;
196+ while ( Date . now ( ) - startTime < timeout ) {
197+ const remainingTime = Math . max ( 50 , timeout - ( Date . now ( ) - startTime ) ) ;
149198
150- const result = await Promise . race ( [ readPromise , timeoutPromise ] ) ;
199+ const readPromise = device . reader . read ( ) ;
200+ const timeoutPromise = new Promise < { value : undefined ; done : true } > ( ( resolve ) =>
201+ setTimeout ( ( ) => resolve ( { value : undefined , done : true } ) , Math . min ( 300 , remainingTime ) )
202+ ) ;
151203
152- if ( result . done ) {
153- break ;
154- }
204+ const result = await Promise . race ( [ readPromise , timeoutPromise ] ) ;
155205
156- if ( result . value && result . value . length > 0 ) {
157- chunks . push ( result . value ) ;
158- // If we received data, wait a bit more for additional data
206+ if ( result . value && result . value . length > 0 ) {
207+ chunks . push ( result . value ) ;
208+ totalBytes += result . value . length ;
209+
210+ // Check if we have a complete frame
211+ const combined = combineChunks ( chunks , totalBytes ) ;
212+ if ( isCompleteFrame ( combined ) ) {
213+ return combined ;
214+ }
215+ } else if ( result . done ) {
216+ // Timeout on this read, but keep trying if we don't have data yet
217+ if ( totalBytes > 0 ) {
218+ // We have some data, wait a bit more then check
159219 await new Promise ( resolve => setTimeout ( resolve , 50 ) ) ;
160220 }
161221 }
162- } catch ( error ) {
163- console . error ( 'Read error:' , error ) ;
164222 }
165223
166- // Combine all chunks
167- const totalLength = chunks . reduce ( ( sum , chunk ) => sum + chunk . length , 0 ) ;
224+ // Return whatever we got
225+ return combineChunks ( chunks , totalBytes ) ;
226+ }
227+
228+ /**
229+ * Helper to combine chunks into a single Uint8Array
230+ */
231+ function combineChunks ( chunks : Uint8Array [ ] , totalLength : number ) : Uint8Array {
168232 const result = new Uint8Array ( totalLength ) ;
169233 let offset = 0 ;
170234 for ( const chunk of chunks ) {
171235 result . set ( chunk , offset ) ;
172236 offset += chunk . length ;
173237 }
174-
175238 return result ;
176239}
177240
178241/**
179- * Send command and receive response
242+ * Check if we have a complete frame based on LEN byte
180243 */
181- export async function transceiveSerial (
182- device : SerialDevice ,
183- command : Uint8Array ,
184- timeout : number = 2000
185- ) : Promise < Uint8Array > {
186- // Clear any pending data first
187- if ( device . reader ) {
188- try {
189- // Quick read to clear buffer
190- const clearPromise = device . reader . read ( ) ;
191- const quickTimeout = new Promise < { done : true } > ( ( resolve ) =>
192- setTimeout ( ( ) => resolve ( { done : true } ) , 50 )
193- ) ;
194- await Promise . race ( [ clearPromise , quickTimeout ] ) ;
195- } catch {
196- // Ignore clear errors
197- }
198- }
244+ function isCompleteFrame ( data : Uint8Array ) : boolean {
245+ if ( data . length < 4 ) return false ;
199246
200- await sendSerialData ( device , command ) ;
247+ const len = data [ 0 ] ;
248+
249+ // Known frame sizes for ASK RDR-518:
250+ // LEN=0x04: no-card response, total 8 bytes (4 + 2 data + 2 CRC)
251+ // LEN=0x0b: card-found response, total 15 bytes
252+ if ( len === 0x04 && data . length >= 8 ) return true ;
253+ if ( len === 0x0b && data . length >= 15 ) return true ;
201254
202- // Small delay to allow device to process
203- await new Promise ( resolve => setTimeout ( resolve , 50 ) ) ;
255+ // Generic check: LEN + 4 bytes (header estimate)
256+ if ( data . length >= len + 4 ) return true ;
204257
205- return await receiveSerialData ( device , timeout ) ;
258+ return false ;
206259}
207260
208261/**
@@ -212,7 +265,7 @@ export function getSerialPortInfo(port: SerialPort): string {
212265 const info = port . getInfo ( ) ;
213266
214267 if ( info . usbVendorId === 0x1fd3 && info . usbProductId === 0x0108 ) {
215- return 'RDR-518 NFC Reader ' ;
268+ return 'ASK RDR-518' ;
216269 }
217270 if ( info . usbVendorId === 0x072f ) {
218271 return 'ACS NFC Reader' ;
0 commit comments