source: PinConnection/USerialPort.pas

Last change on this file was 480, checked in by chronos, 8 years ago
  • Fixed: Serial port state was incorrectly detected on Linux.
File size: 12.1 KB
Line 
1unit USerialPort;
2
3{$mode Delphi}{$H+}
4
5interface
6
7uses
8 {$IFDEF Windows}Windows, {$ENDIF}Registry,
9 Classes, SysUtils, SynaSer, Dialogs, UCommon, UThreading, Syncobjs,
10 DateUtils, FileUtil, SpecializedList;
11
12const
13 MaxPort = 255;
14
15type
16 TBaudRate = (br110, br300, br600, br1200, br2400, br4800,
17 br9600, br14400, br19200, br38400, br56000,
18 br57600, br115200, br128000, br256000);
19 TParity = (paNone, paOdd, paEven, paMark, paSpace);
20 TStopbits = (sb1_0, sb1_5, sb2_0);
21 TDatabits = Integer;
22 TFlowControl = (fcNone, fcSoftware, fcHardware);
23
24 TSerialPort = class;
25 TReceiveDataEvent = procedure(Stream: TListByte) of object;
26
27 { TSerialPortReceiveThread }
28
29 TSerialPortReceiveThread = class(TListedThread)
30 public
31 Parent: TSerialPort;
32 procedure Execute; override;
33 destructor Destroy; override;
34 end;
35
36 { TSerialPort }
37
38 TSerialPort = class(TBlockSerial)
39 private
40 FRTS: Boolean;
41 FDTR: Boolean;
42 FActive: Boolean;
43 FBaudRate: Integer;
44 FDataBits: TDataBits;
45 FFlowControl: TFlowControl;
46 FName: string;
47 FOnReceiveData: TReceiveDataEvent;
48 FParity: TParity;
49 FStopBits: TStopBits;
50 FReceiveThread: TSerialPortReceiveThread;
51 FReceiveBuffer: TListByte;
52 function FindFriendlyName(Key: string; Port: string): string;
53 function GetName: string;
54 procedure GetSerialPortNamesExt(Strings: TStrings);
55 procedure SetBaudRate(const AValue: Integer);
56 procedure SetDataBits(const AValue: TDataBits);
57 procedure SetDTR(const AValue: Boolean);
58 procedure SetFlowControl(const AValue: TFlowControl);
59 procedure SetActive(const AValue: Boolean);
60 procedure SetName(const AValue: string);
61 procedure SetParity(const AValue: TParity);
62 procedure SetRTS(const AValue: Boolean);
63 procedure SetStopBits(const AValue: TStopBits);
64 procedure Open;
65 procedure Close;
66 public
67 Lock: TCriticalSection;
68 property Name: string read GetName write SetName;
69 property FlowControl: TFlowControl read FFlowControl write SetFlowControl;
70 property DataBits: TDataBits read FDataBits write SetDataBits;
71 property StopBits: TStopBits read FStopBits write SetStopBits;
72 property Parity: TParity read FParity write SetParity;
73 property BaudRate: Integer read FBaudRate write SetBaudRate;
74 property Active: Boolean read FActive write SetActive;
75 property RTS: Boolean read FRTS write SetRTS;
76 property DTR: Boolean read FDTR write SetDTR;
77 property ReceiveBuffer: TListByte read FReceiveBuffer;
78
79 property OnReceiveData: TReceiveDataEvent read FOnReceiveData write FOnReceiveData;
80 procedure LoadAvailableToStrings(Strings: TStrings; Check: Boolean = False);
81 constructor Create;
82 destructor Destroy; override;
83 procedure Flush;
84 procedure Purge;
85 procedure Assign(Source: TObject);
86 end;
87
88const
89 BaudRateNumericTable: array[TBaudRate] of Integer = (
90 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000,
91 57600, 115200, 128000, 256000);
92 INVALID_HANDLE_VALUE = DWORD(-1);
93
94implementation
95
96resourcestring
97 SAssignmentError = 'Assignment error';
98 SWrongDataBitsNumber = 'Wrong data bits number %s';
99
100{ TSerialPort }
101
102procedure TSerialPort.SetActive(const AValue: Boolean);
103begin
104 if not FActive and AValue then begin
105 FActive := True;
106 Open;
107 FActive := FHandle <> THandle(INVALID_HANDLE_VALUE);
108 if not FActive then FreeAndNil(FReceiveThread);
109 end else
110 if FActive and not AValue then begin
111 FActive := False;
112 Close;
113 end;
114end;
115
116procedure TSerialPort.SetName(const AValue: string);
117var
118 LastState: Boolean;
119begin
120 if FName = AValue then Exit;
121 LastState := FActive;
122 Active := False;
123 FName := AValue;
124 Active := LastState;
125end;
126
127procedure TSerialPort.SetParity(const AValue: TParity);
128begin
129 FParity := AValue;
130 if FActive then begin
131 GetCommState;
132 DCB.Parity := Integer(AValue);
133 SetCommState;
134 end;
135end;
136
137procedure TSerialPort.SetRTS(const AValue: Boolean);
138begin
139 FRTS := AValue;
140 if FActive then SetRTSF(FRTS);
141end;
142
143procedure TSerialPort.SetStopBits(const AValue: TStopBits);
144begin
145 FStopBits := AValue;
146 if FActive then begin
147 GetCommState;
148 DCB.StopBits := Integer(AValue);
149 SetCommState;
150 end;
151end;
152
153procedure TSerialPort.Open;
154begin
155 Connect(FName);
156 if FHandle <> INVALID_HANDLE_VALUE then begin
157 //set_fDtrControl(DCB, 1);
158 //DCB.flags := ;
159 {$IFDEF DEBUG}
160 RaiseExcept := True;
161 {$ENDIF}
162 SetBaudRate(FBaudRate);
163 SetParity(FParity);
164 SetStopBits(FStopBits);
165 SetDataBits(FDataBits);
166 SetFlowControl(FFlowControl);
167 SetDTR(FDTR);
168 SetRTS(FRTS);
169
170 FReceiveThread := TSerialPortReceiveThread.Create(True);
171 FReceiveThread.FreeOnTerminate := False;
172 FReceiveThread.Parent := Self;
173 FReceiveThread.Name := 'SerialPort';
174 FReceiveThread.Start;
175 end;
176end;
177
178procedure TSerialPort.Close;
179begin
180 if FHandle <> INVALID_HANDLE_VALUE then begin
181 FreeAndNil(FReceiveThread);
182 CloseSocket;
183 end;
184end;
185
186function TSerialPort.FindFriendlyName(Key: string; Port: string): string;
187var
188 r: TRegistry;
189 k: TStringList;
190 i: Integer;
191 ck: string;
192 rs: string;
193begin
194 r := TRegistry.Create;
195 k := TStringList.Create;
196
197 r.RootKey := HKEY_LOCAL_MACHINE;
198 r.OpenKeyReadOnly(key);
199 r.GetKeyNames(k);
200 r.CloseKey;
201
202 try
203 for i := 0 to k.Count - 1 do
204 begin
205 ck := key + k[i] + '\'; // current key
206 // looking for "PortName" stringvalue in "Device Parameters" subkey
207 if r.OpenKeyReadOnly(ck + 'Device Parameters') then
208 begin
209 if r.ReadString('PortName') = port then
210 begin
211 r.CloseKey;
212 r.OpenKeyReadOnly(ck);
213 rs := UTF8Encode(r.ReadString('FriendlyName'));
214 Break;
215 end
216 end
217 // keep looking on subkeys for "PortName"
218 else
219 begin
220 if r.OpenKeyReadOnly(ck) and r.HasSubKeys then
221 begin
222 rs := FindFriendlyName(ck, port);
223 if rs <> '' then Break;
224 end;
225 end;
226 end;
227 result := rs;
228 finally
229 r.Free;
230 k.Free;
231 end;
232end;
233
234procedure TSerialPort.GetSerialPortNamesExt(Strings: TStrings);
235var
236 Reg: TRegistry;
237 l: TStringList;
238 n: integer;
239 pn, fn: string;
240begin
241 l := TStringList.Create;
242 Reg := TRegistry.Create;
243 try
244 Reg.RootKey := HKEY_LOCAL_MACHINE;
245 if reg.OpenKeyReadOnly('HARDWARE\DEVICEMAP\SERIALCOMM') then
246 begin
247 Reg.GetValueNames(l);
248 for n := 0 to l.Count - 1 do
249 begin
250 pn := Reg.ReadString(l[n]);
251 fn := FindFriendlyName('\System\CurrentControlSet\Enum\', pn);
252 if fn <> '' then
253 Strings.Add(pn + Strings.NameValueSeparator + fn)
254 else Strings.Add(pn + Strings.NameValueSeparator + pn)
255 end;
256 end;
257 finally
258 L.Free;
259 Reg.Free;
260 end;
261end;
262
263procedure TSerialPort.LoadAvailableToStrings(Strings: TStrings; Check: Boolean = False);
264var
265 I: Integer;
266 {$IFDEF Linux}Files: TStringList;{$ENDIF}
267 TestPort: TSerialPort;
268begin
269 Strings.Clear;
270 Strings.NameValueSeparator := '|';
271 {$IFDEF Windows}
272 if Check then begin
273 GetSerialPortNamesExt(Strings);
274 // If no ports with friendly names detected try open all ports (compatibility with Win98)
275 if Strings.Count = 0 then
276 try
277 TestPort := TSerialPort.Create;
278 for I := 0 to MaxPort - 1 do
279 with TestPort do begin
280 Name := 'COM' + IntToStr(I);
281 Active := True;
282 if (LastError = ERROR_SUCCESS) or (LastError = ERROR_ACCESS_DENIED) then
283 begin
284 Strings.AddObject(Name, nil);
285 end;
286 Active := False;
287 end;
288 finally
289 TestPort.Free;
290 end;
291 end else begin
292 for I := 1 to MaxPort do
293 Strings.AddObject('COM' + IntToStr(I) + Strings.NameValueSeparator +
294 'COM' + IntToStr(I), nil);
295 end;
296 {$ENDIF}
297 {$IFDEF Linux}
298 if Check then
299 try
300 Files := FindAllFiles('/dev', 'tty*', False);
301 Strings.Assign(Files);
302 for I := 0 to Strings.Count - 1 do
303 Strings.Strings[I] := Strings.Strings[I] + Strings.NameValueSeparator +
304 Strings.Strings[I];
305 finally
306 Files.Free;
307 end else begin
308 for I := 1 to 63 do
309 Strings.AddObject('/dev/ttyS' + IntToStr(I) + Strings.NameValueSeparator +
310 '/dev/ttyS' + IntToStr(I), nil);
311 end;
312 {$ENDIF}
313end;
314
315constructor TSerialPort.Create;
316begin
317 inherited Create;
318 Lock := TCriticalSection.Create;
319 FReceiveBuffer := TListByte.Create;
320 FBaudRate := 9600;
321 FName := 'COM1';
322 FDataBits := 8;
323 FStopBits := sb1_0;
324 FParity := paNone;
325 FlowControl := fcNone;
326 FDTR := False;
327 FRTS := False;
328 Active := False;
329end;
330
331destructor TSerialPort.Destroy;
332begin
333 Active := False;
334 FreeAndNil(FReceiveThread);
335 FreeAndNil(FReceiveBuffer);
336 FreeAndNil(Lock);
337 inherited;
338end;
339
340procedure TSerialPort.Flush;
341begin
342 if FActive then begin
343 inherited Flush;
344 end;
345end;
346
347procedure TSerialPort.Purge;
348begin
349 if FActive then
350 inherited Purge;
351end;
352
353procedure TSerialPort.Assign(Source:TObject);
354begin
355 if Source is TSerialPort then begin
356 Name := TSerialPort(Source).Name;
357 BaudRate := TSerialPort(Source).BaudRate;
358 Parity := TSerialPort(Source).Parity;
359 StopBits := TSerialPort(Source).StopBits;
360 DataBits := TSerialPort(Source).DataBits;
361 FlowControl := TSerialPort(Source).FlowControl;
362 DTR := TSerialPort(Source).DTR;
363 RTS := TSerialPort(Source).RTS;
364 end else raise Exception.Create(SAssignmentError);
365end;
366
367procedure TSerialPort.SetBaudRate(const AValue: Integer);
368begin
369 FBaudRate := AValue;
370 if FActive then begin
371 GetCommState;
372 DCB.BaudRate := AValue;
373 SetCommState;
374 end;
375end;
376
377function TSerialPort.GetName: string;
378begin
379 Result := FName;
380end;
381
382procedure TSerialPort.SetDataBits(const AValue: TDataBits);
383begin
384 if (AValue >= 5) and (AValue <= 8) then FDataBits := AValue
385 else raise Exception.CreateFmt(SWrongDataBitsNumber, [IntToStr(AValue)]);
386 if FActive then begin
387 GetCommState;
388 DCB.ByteSize := AValue;
389 SetCommState;
390 end;
391end;
392
393procedure TSerialPort.SetDTR(const AValue: Boolean);
394begin
395 FDTR := AValue;
396 if FFlowControl = fcNone then
397 DCB.flags := DCB.flags and (not (dcb_DtrControlEnable * 3)) or
398 (dcb_DtrControlEnable * Byte(AValue));
399 if FActive then begin
400 if FFlowControl = fcNone then SetCommState
401 else SetDTRF(FDTR);
402 end;
403end;
404
405procedure TSerialPort.SetFlowControl(const AValue: TFlowControl);
406begin
407 FFlowControl := AValue;
408 if FActive then begin
409 GetCommState;
410 case AValue of
411 fcNone: DCB.flags := 0;
412 fcSoftware: DCB.flags := dcb_OutX or dcb_InX or
413 dcb_DtrControlEnable or dcb_RtsControlEnable;
414 fcHardware: DCB.flags := dcb_OutxCtsFlow or dcb_OutxDsrFlow
415 or dcb_DtrControlHandshake or dcb_RtsControlHandshake;
416 end;
417 SetCommState;
418 end;
419end;
420
421{ TSerialPortReceiveThread }
422
423procedure TSerialPortReceiveThread.Execute;
424var
425 InBufferUsed: Integer;
426 Buffer: array of Byte;
427 Read: Integer;
428begin
429 InBufferUsed := 0;
430 with Parent do repeat
431 if InBufferUsed = 0 then Sleep(1);
432 //else Yield;
433 if Active then
434 try
435 Parent.Lock.Acquire;
436 InBufferUsed := WaitingData;
437 if InBufferUsed > 0 then begin
438 SetLength(Buffer, InBufferUsed);
439 Read := RecvBuffer(Buffer, Length(Buffer));
440 SetLength(Buffer, Read);
441
442 Parent.FReceiveBuffer.Count := Length(Buffer);
443 Parent.FReceiveBuffer.ReplaceBuffer(0, PByte(Buffer)^, Length(Buffer));
444 if Assigned(Parent.FOnReceiveData) then begin
445 try
446 Parent.Lock.Release;
447 Parent.FOnReceiveData(Parent.FReceiveBuffer);
448 finally
449 Parent.Lock.Acquire;
450 end;
451 end;
452 end else InBufferUsed := 0;
453 finally
454 Parent.Lock.Release;
455 end else InBufferUsed := 0;
456 until Terminated;
457end;
458
459destructor TSerialPortReceiveThread.Destroy;
460begin
461 inherited;
462end;
463
464end.
465
Note: See TracBrowser for help on using the repository browser.