Снова о COM-портах

Clarion, Clarion 7

Модератор: Дед Пахом

Правила форума
При написании вопроса или обсуждении проблемы, не забывайте указывать версию Clarion который Вы используете.
А так же пользуйтесь спец. тегами при вставке исходников!!!
Ответить
Гость

Сообщение Гость »

Я дико извиняюсь за очередное поднятие этой темы.
Но никак у меня не получается создать хотя бы простенькую программку, которая могла бы отправлть в модем AT-команду и получать оттуда то, что модем возвращает.
Я уже неделю бьюсь с API-функциями WriteFile и ReadFile, но у меня так ничего и не получается. В частности, не могу понять, как заранее сообщить функции ReadFile количество байт, которые нужно прочитать. Ну... и другие трудности.
Тем более, что Кларионовские прототипы этих функция разные источники дают по-разному - трудно понять, где правда.
Дайте кто-нибудь небольшой РАБОТАЮЩИЙ примерчик по использованию модемных команд (32-битный вариант). Буду признателен.
P.S. Ссылки на CLACOM не предлагать.
Написал: Booroondook(77)
Гость

Сообщение Гость »

Я уже неделю бьюсь с API-функциями WriteFile и ReadFile, но у меня так ничего и не получается. В частности, не могу понять, как заранее сообщить функции ReadFile количество байт, которые нужно прочитать.
параметром nNumberOfBytesToRead, надо полагать.
Тем более, что Кларионовские прототипы этих функция разные источники дают по-разному - трудно понять, где правда.
правда по WinAPI в MSDN.
Дайте кто-нибудь небольшой РАБОТАЮЩИЙ примерчик по использованию модемных команд (32-битный вариант). Буду признателен.
прям вот так сишного кода и накидать?

OVERLAPPED используешь? DCB устанавливаешь? таймауты устанавливаешь?
показал бы хоть свой код, а то так не поймешь, что у тебя не работает. тем более, что в MSDN есть рабочие примеры.
P.S. Ссылки на CLACOM не предлагать.
^^^^^^ ROFL! =8-3

Фр> WBR, Booroondook

--
Best regards,
Maxim Yemelyanov,
Enigma Soft Company
phone: +380 572 177977
WEB: http://enigmasoft.com.ua
e-mail: clalist@enigmasoft.com.ua
ICQ: 12253836
Написал: ClaList(2)
Гость

Сообщение Гость »

"Большое спасибо", Максим!
Поучать всегда легче, чем помогать.
Возможно, в других вопросах, я сильней Вас, и мог бы там Вас поучать, как Вы сейчас пытаетесь меня.
Насчет параметра NumberOfBytesToRead - я понимаю, что параметр назвать можно как угодно.
Вопрос в том - откуда я могу заранее знать, сколько байт считается функцией.
Я ведь пробовал наугад указывать заведомо большее число байт - ни к чему хорошему это ни привело.
Поэтому я и прошу работающий примерчик.
Причем, не на C++ (как в MSDN), а на Кларионе.
И насчет правды - в MSDN действительно есть все - жаль только, что не для Клариона.
Гость

Сообщение Гость »

Каждый в чем-то сильнее другого.
Я задал вопросы и попросил показать код. Чтобы указать, где необходимо внести правки. Как вариант. Как второй вариант - я мог бы привести свой код. Но он на си, а Вы с си, как написали ниже, не дружите.
Насчет параметра NumberOfBytesToRead - я понимаю, что параметр назвать можно как угодно.
Я говорил об MSDN. Описание функции ReadFile.

Код: Выделить всё

BOOL ReadFile(
  HANDLE hFile,                // handle to file
  LPVOID lpBuffer,             // data buffer
  DWORD nNumberOfBytesToRead,  // number of bytes to read
  LPDWORD lpNumberOfBytesRead, // number of bytes read
  LPOVERLAPPED lpOverlapped    // overlapped buffer
);
Вопрос в том - откуда я могу заранее знать, сколько байт считается функцией.
считается 0..nNumberOfBytesToRead байт, в зависимости от к-ва байт во входном буфере и таймаутов чтения.
А сколько должно быть считано - от протокола обмена зависит. Это модем, или какая-то желязяка со специфическим протоколом обмена? Железяки тоже бывают разные, если не считал ответ и не ответил за 200мс, то гайки, начинай обмен снова.
Я ведь пробовал наугад указывать заведомо большее число байт - ни к чему хорошему это ни привело.
Как говорится, чтение документации с выражением - $50 в час.
Тогда откуда у вас информация о функции ReadFile ? Если не знаете, как пользоваться, почему бы не уточнить в документации? Вижу 2 проблемы - отсутствие оной документации вообще, либо незнание английского в достаточной мере для понимания. Возможно тогда вопрос должен был быть задан по другому: где взять русский перевод MSDN?
Поэтому я и прошу работающий примерчик.
Причем, не на C++ (как в MSDN), а на Кларионе.
На кларионе у меня нет.
И насчет правды - в MSDN действительно есть все - жаль только, что не для Клариона.
В MSDN описывает интерфейсы к ОС, которыми можно пользоваться из клариона, делфи, си и всего, что компилит под винду.

--
Best regards,
Maxim Yemelyanov

(Добавление)

Добрый день!

На сайте Гундорова Сергея http://pisoft.ru/verstak/ есть ряд статей Пола Атрайда. Почитай, может поможет

С уважением Мартюшев Леонид
mailto:leonid@opfr.komi.com

(Добавление)

Некоторое время назад я кидал в рассылку реально работающий пример работы с СОМ-портом. На clarionlife.net его нет к сожалению.
Попоздже закину.

С уважением, Андрей Истомин

Ну это не мне, а тому, кто вопрошал, но думаю, если примерчик будет на http://clarionlife.net, то хорошо

С уважением Мартюшев Леонид
mailto:leonid@opfr.komi.com
Написал: ClaList(2)
Гость

Сообщение Гость »

Спасибо за ссылку на статью Paul Attryde.
Почитаю.
Собственно говоря, задача такая - послать в мобильный телефон запрос в виде произвольной AT-команды и получить в ответ возвращаемую информацию. Т.е., грубо говоря, задача эмуляции терминала.
Почему я прошу дать реально работающий пример. Потому что ни в MSDN, ни в статьях различных... нигде не дается последовательность вызова этих АПИшных функций.
Ну хорошо, я инициализировал порт (это мне удалось). Создал DCB. С этим тоже все нормально. Настроил параметры порта (скорость, четность и т.д.).
Потом, по идее наждо делать WriteFile. Сделал. Удачно.
А дальше-то что?
Я пробую сразу сделать ReadFile, но ничего толкового не возвращается.
Я подозреваю, что между WriteFile и ReadFile нужно вызывать еще какие-то функции.
Но какие?
Ни в одном источнике я не смог найти связанной информации, описывающей нужную последовательность вызова функций - только какие-то разрозненные обрывки, описывающие отдельные функции.

Написал: Booroondook(77)
Гость

Сообщение Гость »

Можно делать обмен синхронный, можно асинхронный. Лови кусок кода на си, надеюсь, поможет разобраться что за чем должно вызываться.
На самом деле в случае синхронного обмена без колбеков все _намного_ проще. Но я не смог найти этот код :(

Идея такая: после открытия порта стартует апишный поток, который мониторит порт и вызывает колбековую функцию, передавая в нее принятые данные.

Код: Выделить всё

/*!WinAPI-обертка для #WatcherThread_inner, чтобы WatcherThread_inner был членом класса*/
static u_long WINAPI WatcherThreadProc(void* lpData)
{
    TComm *comm = reinterpret_cast<TComm*>(lpData);
    comm->WatcherThread_inner();
    return 0;
}

/* функция потока. код выглядит уродливо, т.к. писался давно, когда я не умел
программировать еще больше, чем не умею сейчас. но работает.
*/
void TComm::WatcherThread_inner()
{
    #define maxBuffer 100
    u_long dwEvMask,dwBytesRead;
    char sBuf[maxBuffer+1];
    OVERLAPPED OvlWatcher;
    HANDLE Handles[2]; // будем ловить 2 события
    
    if (!SetCommMask( hComm, EV_RXCHAR)) return;
    if (!(OvlWatcher.hEvent = CreateEvent(NULL,1,0,NULL))) return;
    
    Handles[0] = hExitClose;  // событие принудительного закрытия порта
    Handles[1] = OvlWatcher.hEvent; // а также событие появления данных в порту

    while (!IsClose())
    {
        // ловим событие на порту
        if (! WaitCommEvent( hComm, &dwEvMask, &OvlWatcher ))
        {
            switch( WaitForMultipleObjects(2,(HANDLE*)&Handles, FALSE, INFINITE) )
            {
             // выход, прерываем цикл (break тут - выход из switch, а не while)
            case WAIT_OBJECT_0: continue;
            case WAIT_OBJECT_0+1: // поймали событие в порту
                // анализируем состояние, заполняя структуру OVERLAPPED
                if (!GetOverlappedResult( hComm, &OvlWatcher, &dwBytesRead, FALSE))
                    continue;
                ResetEvent(OvlWatcher.hEvent); // сбрасываем event в этой структуре
                break;
            }
        }
        if (IsClose()) break;  // флаг закрытия
        
        // на самом деле, ловится много событий.
        // если событие == пришли данные
        if (dwEvMask==EV_RXCHAR)
        do
        {
            dwBytesRead = ReadBlockDirect( sBuf, maxBuffer); // вызывает ReadFile
            if (dwBytesRead<0) break;
            Queue->Write(sBuf, dwBytesRead ); // засемафорено пишем в очередь

            if (port_callback) // если зарегистрирован колбек,
                port_callback(sBuf, dwBytesRead); // то вызываем его

            sBuf[dwBytesRead]=0;
            // пишем в лог. log - экземпляр класса с перегруженным манипулятором endl.
            log << sBuf << endl(DET_DEBUG);
        }
        while(dwBytesRead==maxBuffer);
        SetEvent(hEvent);
    }//while
    
    CloseHandle( OvlWatcher.hEvent );
}
--
Best regards,
Maxim Yemelyanov
Написал: ClaList(2)
Гость

Сообщение Гость »

Спасибо. Буду пробовать разбираться.
Я, кстати, времени не терял, а пытался писать в соответствии со статьей Paul Attryde.
Вот, что у меня получилось (естественно, здесь не весь код и опущены декларации всякие):

Код: Выделить всё

        PortNumber=GETINI('Communications','PortNumber',4,'.\Wcomm.ini') !Читаем номер порта из ИНИ-файла
        Loc:InBuffer=GETINI('Communications','InBuffer',1024,'.\Wcomm.ini') !Входной буфер
        Loc:OutBuffer=GETINI('Communications','OutBuffer',4096,'.\Wcomm.ini') !Выходной буфер
        DCB:BaudRate=GETINI('Communications','BaudRate',57600,'.\Wcomm.ini')  !Скорость
        DCB:Parity=GETINI('Communications','Parity',0,'.\Wcomm.ini') !Четность в виде числа
        CASE DCB:Parity                                              !Преобразование четности из числа в букву
         OF 1
           Loc:ParityS='Y'
         ELSE
           Loc:ParityS='N'
        END
        DCB:ByteSize=GETINI('Communications','ByteSize',8,'.\Wcomm.ini') !Размер байта
        DCB:StopBits=GETINI('Communications','StopBits',1,'.\Wcomm.ini') !Стоповые биты
        
        
        CTO:ReadIntervalTimeout=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')        !Тайм-ауты - все по 5 секунд
        CTO:ReadTotalTimeoutMultiplier=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
        CTO:ReadTotalTimeoutConstant=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
        CTO:WriteTotalTimeoutMultiplier=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
        CTO:WriteTotalTimeoutConstant=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
        
        Loc:PortString = 'COM' & CLIP(PortNumber) !Текстовое имя порта
        IF Loc:PortHandle < 1
         Loc:PortHandle = CreateFile(Address(Loc:PortString), GENERIC_READ + GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, NULL)
        END
        IF Loc:PortHandle=-1
         MESSAGE('Невозможно создать порт' & '<10,13>' & Loc:PortHandle)
         RETURN
        ELSE
         !MESSAGE(Loc:PortHandle)
        END
        
        IF Loc:PortHandle <> -1
                                       !Формируем строку порта вида "9600,N,8,1"
         Loc:ControlString=DCB:BaudRate & ',' & Loc:ParityS & ',' & DCB:ByteSize & ',' & DCB:StopBits
         MESSAGE('Loc:ControlString: ' & Loc:ControlString, 'ControlString')
         IF BuildCommDCB(Address(Loc:ControlString), Address(DCB)) <> 0
           Loc:ReturnCode=SetCommState(Loc:PortHandle, Address(DCB))
           IF ~Loc:ReturnCode
            MESSAGE(Loc:ReturnCode)
           END
           Loc:ReturnCode=SetCommTimeOuts(Loc:PortHandle,Address(COMMTIMEOUTS)) !Тайм-ауты. А надо ли их настраивать?
           IF ~Loc:ReturnCode
            MESSAGE(Loc:ReturnCode)
           END
           Loc:ReturnCode=SetupComm(Loc:PortHandle,Loc:InBuffer,Loc:OutBuffer) 
           IF ~Loc:ReturnCode
            MESSAGE(Loc:ReturnCode)
           END
         ELSE
          MESSAGE('ERROR')
         END
        END
        
        Loc:ReturnCode=GetCommState(Loc:PortHandle,Address(DCB))
        IF ~Loc:ReturnCode
         MESSAGE('GetCommState: ' & Loc:ReturnCode,'GetCommState')
        ELSE
         MESSAGE( !
             'BaudRate: ' & DCB:BaudRate & '<13,10>' & !
             'Parity: '   & DCB:Parity   & '<13,10>' & !
             'ByteSize: ' & DCB:ByteSize & '<13,10>' & !
             'StopBits: ' & DCB:StopBits               !
                            ,'GetCommState'            )
        END
        
        
        Loc:OutString='AT +MPBR=?'
        Loc:Len=LEN(Clip(Loc:OutString))
        Loc:ReturnCode = WriteFile(Loc:PortHandle, Address(Loc:OutString), Loc:Len, Address(Loc:BytesWritten), NULL)
        IF Loc:Len <> Loc:BytesWritten
         MESSAGE('Не записали все байты, которые хотели...')
        ELSE
         !MESSAGE('Записано байт: ' & Loc:BytesWritten & ', код возврата: ' & Loc:ReturnCode, 'WriteFile')
        END
        
        Loc:ReturnCode=SetCommMask(Loc:PortHandle, !
                EV_RXCHAR           + !
                EV_RXFLAG           + !
                EV_TXEMPTY          + !
                EV_CTS              + !
                EV_DSR              + !
                EV_RLSD             + !
                EV_BREAK            + !
                EV_ERR              + !
                EV_RING             + !
                EV_PERR             + !
                EV_RX80FULL         + !
                EV_EVENT1           + !
                EV_EVENT2           ) !Устанавливаем маску на все события
        IF ~Loc:ReturnCode
         MESSAGE('SetCommMask: ' & Loc:ReturnCode,'SetCommMask')
        END
                                     
        
        Loc:ReturnCode = WaitCommEvent(Loc:PortHandle, Address(COMMMASK), NULL) !возвращает не ноль, если все нормально
        IF ~Loc:ReturnCode
         MESSAGE('WaitCommEvent: ' & Loc:ReturnCode,'WaitCommEvent')
        END
        
        
        Loc:ReturnCode = ClearCommError(Loc:PortHandle, Address(Loc:PortErrorCode), Address(ComStat))
        IF ~Loc:ReturnCode
         MESSAGE('ClearCommError: ' & Loc:ReturnCode,'ClearCommError')
        END
        MESSAGE('InQue: ' & CS:InQue & '<13,10>OutQue: ' & CS:OutQue)
        
        Loc:BytesToRead=CS:InQue
        Loc:ReturnCode = ReadFile(Loc:PortHandle, Address(Loc:TempInString), Loc:BytesToRead, Address(Loc:BytesRead), NULL)
        IF ~Loc:ReturnCode
         MESSAGE('Loc:ReturnCode: ' & Loc:ReturnCode,'ReadFile')
        ELSE
         IF ~Loc:BytesRead
          MESSAGE('Loc:BytesRead: ' & Loc:BytesRead,'ReadFile')
         ELSE
          Loc:InString = Loc:InString & Loc:TempInString & '<13,10,13,10>' !Дописывание двойного перевода строки
         END
         DISPLAY()
Вот у меня почему-то GetCommState возвращает StopBits=0, хотя я задаю 1
И самое главное - WaitCommEvent выдает 0.
Кстати, WriteFile работает нормально - это было проверено утилитой мониторинга порта.
И кроме того, несмотря на то, что эта программа не возвращает отклик от модема, этот отклик "застревает" в модеме. Ибо можно после посыла строки вфйти из программы, а потом запустить другую программу эмуляции терминала (не мою), и там на команду 'AT' получить отклик на ту команду, что посылал я из своей программы.
Я подозреваю, что у меня функции вызываются не в том порядке. Или какие-то пропущены.
Написал: Booroondook(77)
Гость

Сообщение Гость »

!Тайм-ауты - все по 5 секунд
Кхе-кхе. А вот это зря. Смысл таймаутов - коэффициенты умножения и сложения. С таким раскладом винда будет ждать ответа от модема Loc:BytesToRead * 5 + 5 секунд. При килобайтном буфере получим 85 минут.....
IF BuildCommDCB(Address(Loc:ControlString), Address(DCB)) <> 0
Честно говоря, ни разу не пользовался BuildCommDCB, использую SetCommState, там можно прочитать/установить поля побитно.
Loc:ReturnCode = WaitCommEvent(Loc:PortHandle, Address(COMMMASK), NULL) !возвращает не ноль, если все нормально
Вот тут непонятно. Раз ты открыл порт без OVERLAPPED, то зачем тебе WaitCommEvent? проще войти в ReadFile и заблокироваться до получения данных.
Если же хочешь варианта работы без блокирования на чтении из порта, то используй FILE_FLAG_OVERLAPPED при открытии порта и WaitCommEvent, чтобы узнать, когда данные пришли.
Кстати, WriteFile работает нормально - это было проверено утилитой мониторинга порта.
А что возвращает и какой GetLastError после вызова ReadFile?

--
Best regards,
Maxim Yemelyanov
Написал: ClaList(2)
Гость

Сообщение Гость »

А что возвращает и какой GetLastError после вызова ReadFile?
Возвращает 0
Написал: Booroondook(77)
Гость

Сообщение Гость »

Кто из них?

--
Best regards,
Maxim Yemelyanov
Написал: ClaList(2)
Гость

Сообщение Гость »

Как это "кто из них"?
У меня там ReadFile только один раз вызывается. Вот она ноль и возвращает.
Написал: Booroondook(77)
Гость

Сообщение Гость »

0 для ReadFile - это ошибка.
Что в таком случае возвращает GetLastError ?

--
Best regards,
Maxim Yemelyanov
Написал: ClaList(2)
Гость

Сообщение Гость »

GetLastError возвращает тоже 0.
Я же говорю - у меня подозрение (наверное, 100%-ное), что я все эти функции вызываю не в том порядке, как нужно.
Может, какие-то вообще забыл вызвать.
Я пользовался для справки несколькими источниками - везде совершенно разные рекомендации, везде совершенно разные описания API-функций (указывают разные типы данных) в нотации Клариона.
Короче, истину найти совершенно невозможно.
Собственно, поэтому я и просил дать работающий примерчик терминала на Кларионе - где просто посылается АТ-команда и появляется ответ от модема.
Собственно, я даже где-то откопал подобный примерчик - он называется WComm32.zip. Написан Игорем Куклиным 20 октября 1999 г.
Там по идее есть все, что мне нужно.
Но... Если его скомпилировать под С5/С55/С6, то программа просто молча вываливается при вызове терминала (там терминал вызывается пунктом меню в главном окне приложения).
А если скомпилировать под С4, то даже запускается окно терминала, но через 2-3 секунды программа сваливается по GPF.
Я попытался найти причины такого поведения, но моих знаний тут явно не хватило.
Может быть, прислать вам этот примерчик, а вы посмотрите?
Написал: Booroondook(77)
Ответить