:Class COM ⍝ Class for Asynchronous Serial Communication ⍝ List of methods: ⍝ Open = Open the serial port ⍝ SendReceive = Send the bytes and return the bytes received ⍝ Close = Close the serial port ⍝ Version 1.0 January 2016 ⍝ .Net assemblies: :Using :Using System,mscorlib.dll :Using System.Net,System.dll :Using System.Net.Sockets,System.dll :Using System.IO.Ports,System.dll (⎕IO ⎕ML ⎕WX)←1 3 3 ⍝ System variables :Field Public spObj ⍝ SerialPort object :Field Public Type←'COM' ⍝ Used internally :Field _buffer ⍝ empty Byte[] array same size as _BufferSize :Field _stream ⍝ spObj.BaseStream ⍝ YOU CAN CHANGE THE VALUE OF THE FOLLOWING 5 FIELDS AFTER ⍝ INTANTIATING THE CLASS BUT BEFORE CALLING THE 'Open' METHOD. ⍝ Size of the Input and Output Buffers in Bytes. Should be ⍝ as Big as the Largest Number of Bytes to Send or Receive. :Field Public _BufferSize←100 ⍝ Time in milliseconds to Wait for a port that is not Active (Receiving Nothing). ⍝ If exceeded Windows Stop Waiting and returns the Control to the Calling Application. :Field Public _PortTimeOut←250 ⍝ Time in milliseconds that Windows Waits between two Bytes to Arrive. ⍝ If exceeded Windows Stop Waiting and Returns the Control to the Calling Application. :Field Public _LastByteTimeOut←15 ⍝ When used for serial half duplex communication you will need a small delay ⍝ between sending and receiving to let the serial port switch from receiving to sending. ⍝ Rule of thumb for the delay in millisec: 1E6÷(Baud Rate of serial device) :Field Public _SwitchingDelay←50 ⍝ Default Flow Control ⍝ (0=None, 1=XOn/XOff Software, 2=RequestToSend, 3=RequestToSendXOnXOff) :Field Public _FlowControl←0 ∇ Init0 :Access Public :Implements Constructor spObj←⎕NULL ∇ ∇ r←Open(PortNo BaudRate DataBits Parity StopBits);BaudRateOK :Access Public ⍝ Open a Serial Communication Port and Obtain a .Net Serial Port Object (spObj) ⍝ ⍝ PortNo = Communication Port Number (ex.: 1, 2, 3, 4, etc.) ⍝ BaudRate = Baud Rate (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 76800, 115200) ⍝ DataBits = Number of Data Bits (4, 5, 6, 7, 8) ⍝ Parity = Number for Parity (0 = None, 1 = Odd, 2 = Even, 3 = Mark, 4 = Space) ⍝ StopBits = Number of Stop Bits (1, 1.5, 2) ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Literal Error if Failure ⍝ Check if the port is already opened :If ⎕NULL≢spObj :AndIf spObj.IsOpen r←0 'Error Opening: Port already open' ⋄ →0 :EndIf BaudRateOK←300 600 1200 2400 4800 9600 19200 38400 57600 76800 115200 ⍝ Verify that there is some Serial Port(s) available on the computer :If 0=⍴SerialPort.GetPortNames r←0 'COM Error Opening: No Serial Port Available' ⋄ →0 :EndIf ⍝ Check if the PortNo is available on the computer :If ~PortNo∊{⍎(⍵∊⎕D)/⍵}¨SerialPort.GetPortNames r←0 'COM Error Opening: The Comm Port Number is Not Valid' ⋄ →0 :EndIf ⍝ Validate the rest :If ~BaudRate∊BaudRateOK ⋄ r←0 'Error Opening: The Baud Rate is Not Valid' ⋄ →0 ⋄ :EndIf :If ~DataBits∊4 5 6 7 8 ⋄ r←0 'Error Opening: The Data Bits are Not Valid' ⋄ →0 ⋄ :EndIf :If ~Parity∊0 1 2 3 4 ⋄ r←0 'Error Opening: The Parity is Not Valid' ⋄ →0 ⋄ :EndIf :If ~StopBits∊1 1.5 2 ⋄ r←0 'Error Opening: The Stop Bits are Not Valid' ⋄ →0 ⋄ :EndIf :If ~_FlowControl∊0 1 2 3 ⋄ r←0 'Error Opening: The Flow Control is Not Valid' ⋄ →0 ⋄ :EndIf :Trap 0 ⍝ Construct a SerialPort Object spObj←⎕NEW SerialPort(⊂'COM',⍕PortNo) spObj.(ReadBufferSize WriteBufferSize)←_BufferSize ⍝ Set the Sizes of the Input and Output Buffers spObj.(ReadTimeout WriteTimeout)←_PortTimeOut ⍝ Set the Time-Outs of the communication spObj.BaudRate←BaudRate ⍝ Set the Baud Rate spObj.DataBits←DataBits ⍝ Set the DataBits spObj.Parity←Parity ⍝ Set the Parity spObj.StopBits←(-⎕IO)+(0 1 2 1.5)⍳StopBits ⍝ Set the StopBits spObj.Handshake←_FlowControl ⍝ Set the FlowControl ⍝ Open the Port and empty the buffers spObj.Open spObj.DiscardInBuffer spObj.DiscardOutBuffer ⍝ Success _buffer←Array.CreateInstance(Byte _BufferSize) ⍝ empty Byte[] array same size as _BufferSize _stream←spObj.BaseStream r←1 :Else {}Close ⍝ Show the error. :If 90=⎕EN ⍝ .Net Error r←0('COM Error Opening: ',⎕EXCEPTION.GetBaseException.Message) :Else ⍝ APL Error r←0(1⊃⎕DM),': ',{(' '=1↑⍵)↓((1↓a,0)∨a←' '≠⍵)/⍵}(2⊃⎕DM) :EndIf :EndTrap ∇ ∇ r←{option}SendReceive bytes;count;data;lastByte;qtyBytes;task;taskResult :Access Public ⍝ Write Bytes Asynchronously to a Serial Port. ⍝ Normally the last bytes received will be the one that has time-out on _PortTimeOut. ⍝ Optionnally you can specified the value of the last byte (lastByte) or the quantity ⍝ of bytes to be received (qtyBytes) so the communication will be faster. ⍝ bytes = Bytes (as Numbers 0 to 255) to be Written to the Communication Port ⍝ option = positive number -> lastByte to be received ⍝ = negative number -> qtyBytes to be received ⍝ = absent -> will time-out on _PortTimeOut ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Response if Success, Literal Error if Failure ⍝ Check if the port is already opened :If ⎕NULL≡spObj :OrIf ~spObj.IsOpen r←0 'COM Error Sending: Port not open' ⋄ →0 :EndIf ⍝ Parse the option :If 0≠⎕NC'option' :If option<0 qtyBytes←|option :Else lastByte←option :EndIf :EndIf :Trap 0 ⍝ Write the data: :If _stream.CanWrite :AndIf spObj.IsOpen ⍝ Purge the Input and Output Buffers spObj.DiscardInBuffer spObj.DiscardOutBuffer ⍝ Write the bytes to serial port task←_stream.WriteAsync(bytes 0(⍬⍴⍴,bytes)) ⍝ Wait asynchronously for the task to complete :If 0=⎕TSYNC{task.Wait ⍵}&(_PortTimeOut) r←0 'COM Error Sending: Task did not complete sending the bytes' ⋄ →0 :Else ⍝ Success task.Dispose :EndIf :Else r←0 'COM Error Sending: Serial Port no longer available to send data' ⋄ →0 :EndIf ⍝ Read the data received: data←⍳0 ⋄ taskResult←1 ⍝ Switching delay ⎕TSYNC ⎕DL&_SwitchingDelay÷1000 :If _stream.CanRead :AndIf spObj.IsOpen :While taskResult task←_stream.ReadAsync(_buffer 0 _BufferSize) ⍝ _buffer will be filled with the bytes received ⍝ Waiting asynchronously for the task to complete :If taskResult←⎕TSYNC{task.Wait ⍵}&(_PortTimeOut) ⍝ taskResult=0 when no data is received count←task.Result ⍝ count = number of byte(s) received data,←count↑⌷_buffer ⍝ cumul the bytes :If 0≠⎕NC'lastByte' :AndIf lastByte=¯1↑data ⍝ Check if lastByte has been received →END :EndIf :If 0≠⎕NC'qtyBytes' :AndIf qtyBytes=⍴,data ⍝ Check if the total number of bytes has been received →END :EndIf task.Dispose ⎕TSYNC ⎕DL&_LastByteTimeOut÷1000 ⍝ Wait for last byte to time-out and loop again :EndIf :EndWhile :Else r←0 'COM Error Sending: Serial port no longer available to receive data' ⋄ →0 :EndIf END: ⍝ Prepare the result :If 0≠⍴,data r←1 data :Else r←0 'COM: No data received' :End :Else ⍝ Show the error. :If 90=⎕EN ⍝ .Net Error r←0('COM Error Sending: ',⎕EXCEPTION.GetBaseException.Message) :Else ⍝ APL Error r←0(1⊃⎕DM),': ',{(' '=1↑⍵)↓((1↓a,0)∨a←' '≠⍵)/⍵}(2⊃⎕DM) :EndIf :EndTrap ∇ ∇ r←Close :Access Public ⍝ Close the Serial Port ⍝ The best practice for any application is to wait for some amount of time after calling the Close method ⍝ before attempting to call the Open method, as the port may not be closed instantly. ⍝ ⍝ r[1] = 1 for Success, 0 for Failure ⍝ r[2] = Literal Error if Failure :If ⎕NULL≡spObj ⋄ r←0 'Error Closing: Already Closed' ⋄ →0 ⋄ :EndIf :Trap 0 spObj.Close spObj.Dispose r←1 :Else ⍝ Show the error. :If 90=⎕EN ⍝ .Net Error r←0('COM Error Closing: ',⎕EXCEPTION.GetBaseException.Message) :Else ⍝ APL Error r←0(1⊃⎕DM),': ',{(' '=1↑⍵)↓((1↓a,0)∨a←' '≠⍵)/⍵}(2⊃⎕DM) :EndIf :EndTrap spObj←⎕NULL ∇ :EndClass