diff --git a/SerialPort.pas b/SerialPort.pas index 030da1c..f647605 100644 --- a/SerialPort.pas +++ b/SerialPort.pas @@ -88,6 +88,14 @@ function SerCFlags(ByteSize: Integer; Parity: TParityType; StopBits: Integer; Flags: TSerialFlags): Cardinal; {$ENDIF} +{ Имя устройства в той форме, которую понимает CreateFile под Windows. + ★COM10 и выше открываются ТОЛЬКО как \\.\COM10: короткое имя система + резолвит лишь для COM1..COM9, а у USB-переходников номер за десяток заезжает + легко. Функция чистая и считает одинаково на всех платформах (ради стенда), + а зовёт её только Windows-ветка SerOpen: на Unix имя порта — это путь, и + трогать его нечего. } +function SerWinDeviceName(const Name: string): string; + { Имя порта по умолчанию для настроек и подсказок в UI: у каждой системы своё, и '/dev/ttyS0' на маке — заведомо неверная подсказка. Index 0..N. } function SerDefaultPortName(Index: Integer): string; @@ -128,7 +136,7 @@ end; function SerOpen(const DeviceName: string): TSerialHandle; begin - Result := Serial.SerOpen(DeviceName); + Result := Serial.SerOpen(SerWinDeviceName(DeviceName)); // ★RTL под Windows отдаёт 0 при неудаче, под Unix −1. Приводим к одному. if Result = 0 then Result := SER_INVALID_HANDLE; end; @@ -385,6 +393,28 @@ end; // ── Общее для всех платформ ──────────────────────────────────────────────── +function SerWinDeviceName(const Name: string): string; +var + S: string; + i, N: Integer; +begin + Result := Name; + S := Trim(Name); + // Уже полная форма, или это вовсе не COM-порт (например, путь на Unix). + if (Length(S) < 4) or (Copy(S, 1, 4) = '\\.\') then Exit; + if UpperCase(Copy(S, 1, 3)) <> 'COM' then Exit; + N := 0; + for i := 4 to Length(S) do + begin + if (S[i] < '0') or (S[i] > '9') then Exit; // COM3:, COM-что-то — не трогаем + N := N * 10 + (Ord(S[i]) - Ord('0')); + if N > 999 then Exit; + end; + // COM1..COM9 открываются и коротким именем — оставляем как ввёл оператор, + // чтобы не менять поведение там, где оно и так работало. + if N >= 10 then Result := '\\.\' + S; +end; + function SerValid(Handle: TSerialHandle): Boolean; begin // Ноль сюда попадает от RTL под Windows (там это признак неудачи) и не может diff --git a/test/serial/README.md b/test/serial/README.md index 2895f3a..45569df 100644 --- a/test/serial/README.md +++ b/test/serial/README.md @@ -12,6 +12,12 @@ test/serial/run.sh * **Открытие**: несуществующий порт даёт `SER_INVALID_HANDLE` (а не ноль и не случайное число), живой — годный хендл. +* ★**Имя устройства для Windows**: `COM10` и выше `CreateFile` открывает только + в форме `\\.\COM10` — короткое имя система резолвит лишь для `COM1..COM9`, а у + USB-переходников номер за десяток заезжает легко. Правило чистое + (`SerWinDeviceName`), поэтому проверяется и на Linux: короткие имена не + трогаются, длинные получают префикс, уже полная форма не удваивается, пути + Unix не задеваются. * **termios обратным чтением**: скорость, стоп-биты, `CREAD`/`CLOCAL`, сырой режим (нет `ICANON`, `ECHO`, `OPOST`). * ★**Аппаратный поток `CRTSCTS` не включается сам** — только по явной просьбе. diff --git a/test/serial/serialtest.pas b/test/serial/serialtest.pas index 52413d1..7e1dfa0 100644 --- a/test/serial/serialtest.pas +++ b/test/serial/serialtest.pas @@ -120,6 +120,33 @@ begin fpClose(Master); end; +// ── A2. Имя устройства для Windows ───────────────────────────────────────── +procedure TestWinName; +// ★COM10 и выше CreateFile открывает ТОЛЬКО в форме \\.\COM10 — короткое имя +// система резолвит лишь для COM1..COM9, а у USB-переходников номер за десяток +// заезжает легко. Функция чистая, поэтому проверяется и на Linux. +begin + WriteLn('A2. Имя устройства для Windows (COM10 и выше)'); + Check('COM1 остаётся коротким', SerWinDeviceName('COM1') = 'COM1'); + Check('COM9 остаётся коротким', SerWinDeviceName('COM9') = 'COM9'); + Check('★COM10 получает префикс', + SerWinDeviceName('COM10') = '\\.\COM10', SerWinDeviceName('COM10')); + Check('COM123 получает префикс', + SerWinDeviceName('COM123') = '\\.\COM123', SerWinDeviceName('COM123')); + Check('строчные буквы тоже узнаются', + SerWinDeviceName('com12') = '\\.\com12', SerWinDeviceName('com12')); + Check('пробелы вокруг имени не мешают', + SerWinDeviceName(' COM12 ') = '\\.\COM12', SerWinDeviceName(' COM12 ')); + Check('уже полная форма не удваивается', + SerWinDeviceName('\\.\COM12') = '\\.\COM12'); + Check('путь Unix не трогаем', + SerWinDeviceName('/dev/ttyUSB0') = '/dev/ttyUSB0'); + Check('/dev/cu.* не трогаем', + SerWinDeviceName('/dev/cu.usbserial-A1') = '/dev/cu.usbserial-A1'); + Check('нечисловой хвост не трогаем', SerWinDeviceName('COM3:') = 'COM3:'); + Check('пустое имя не ломает', SerWinDeviceName('') = ''); +end; + // ── B. Настройка порта ────────────────────────────────────────────────────── procedure TestParams; var @@ -320,6 +347,7 @@ begin Failed := 0; WriteLn('=== Стенд последовательного порта (SerialPort.pas на pty) ==='); TestOpen; + TestWinName; TestParams; TestIO; TestCatThrough;