Работа с COM портом на C++ в Windows
Последовательные порты полюбились разработчикам за их простоту в обслуживании и использовании.
И конечно же писать в консоль терминальной программы это всё хорошо, но хочеться своё приложение, которое по нажатии клавиши на экране выполняет нужные вам действия 😉
В этой статье опишу как работать с com портом на языке Си++.
Решение простое, но почемуто рабочий пример найден был не сразу. За сим сохраняю его тут.
Конечно вы можете использовать кроссплатформенные решения вроде QSerial — библиотеки в составе Qt, я наверное так и сделаю, но в будующем. Сейчас же речь о «чистом» виндовском C++. Писать будем в Visual Studio. У меня 2010, хотя роли это никакой не играет.
Создаём новый консольный Win32 проект.
Инклудим header файлы:
Объявляем обработчик com порта:
Я делаю это глобально, чтобы не заморачиваться с указателями при передаче его в функции.
Дальше начинаем формировать функцию main:
Терпеть не могу виндовский стиль программирования. Обозвали всё посвоему и сидят радуются.
Теперь магия объявления строки с именем порта. Дело в том, что char оно преобразовывать само не умеет.
Работа с последоавательными портами в Windows проходит как с файлом. Открываем первый ком порт для записи/чтения:
Теперь нужно настроить параметры соединения:
На msdn советуют сначала получить параметры, а затем менять их. Мы ещё только учимся, поэтому делаем как просят.
Теперь объявим строку, которую будем передавать и переменные необходимые для этого:
Посылаем строку. Напомню, что пример простейший, поэтому никаких особо проверок я не делаю:
Также я решил вывести для контроля размер строки и количество отосланных байт:
В конце программы делаем бесконечный цикл чтения данных:
Теперь функция чтения:
Вот собственно и весь пример.
Я создал виртуальный com порт. И слал из COM1 в COM2:
ReadComPort — программа чтения данных с COM порта
Получилось так, что на одной из АЗС понадобилось обрабатывать данные приходящие в COM порт с внешнего устройства и передать эти данные не в 1с. От этого задания и родилась выложенная утилита. Когда я сел разбираться с маркировкой товаров для ККМ, то она неожиданно пригодилась! А потом подвернулся сканер ШК honeywell, который не хотел с первого раза подключаться к Рознице 2.3, а нетерпеливые покупатели уже стучали ножками и начинали закипать. (как в последствии выяснилось не был прописан суффикс ). Легким движением руки были добавлены несколько строк в ЧекККМ и через ПодключитьОбработчикОжидания торговля восстановилась. Затем утилита прижилась в самописной конфигурации, где понадобилось обрабатывать штрихкоды. В общем выкладываю – может кому и пригодится.
Как это работает:
rem ReadComPort.exe COMХ Скорость СтопБит Четность ЧислоБит КопитьПрочитанныеДанные
ReadComPort.exe COM9 9600 1 0 8 1
1.Это 32 битное приложение.
2.Программа не позволяет запустить себя дважды.
3.При запуске без параметров выдает инструкцию
4.При запуске с параметрами свертывается в SysTray
5.После получения данных из COM порта записывает данные в каталог запуска в папке DATA
6.Имя файла содержит ГГГГММДД_ЧЧММСС_МИЛЛИСЕК.dat – если отсортировать в каталоге по имени, то получим данные в порядке поступления
7.Файлы копятся и не удаляются – удаляйте самостоятельно после обработки
8.Есть возможность сразу посмотреть, что приходит в COM. Для этого идем в SysTray и кликаем на ярлыке. В появившемся меню выбираем пункт «Показать». Если в руках сканер ШК – пикаем и в окне видим что пришло в порт
Первая строка: дата события
Вторая: данные в HEX
Третья: данные в строковом виде
9.Максимально обрабатывает 400 байт
10.Что бы завершить работу надо кликнуть на «Выход»
Как приспособить в 1с.
Гарантия возврата денег
ООО «Инфостарт» гарантирует Вам 100% возврат оплаты, если программа не соответствует заявленному функционалу из описания. Деньги можно вернуть в полном объеме, если вы заявите об этом в течение 14-ти дней со дня поступления денег на наш счет.
Программа настолько проверена в работе, что мы с полной уверенностью можем дать такую гарантию. Мы хотим, чтобы все наши покупатели оставались довольны покупкой.
Для возврата оплаты просто свяжитесь с нами.
C# com-порт получение информации, обработка старт-бита, стоп-бита?
Прошу помощи в понимании, а может и в решении интересующего меня вопроса.
Сначала обрисую суть поставленного передо мной задания, предупрежу сразу в программировании я, так сказать, очень молодой и это в какой-то степени мой первый проект, который я хочу реализовать на платформе visual studio.
Задача:
Есть некое устройство, которое соединяется с компьютером по RS-485 и передает данные 38400 бит/сек, устройство может передавать пакеты с дискретностью 25 Гц или 50Гц.
Командно-информационный пакет, передаваемый устройством, состоит из 6 блоков, в начале каждого пакета передается маркер начала пакета «0xFF, 0xFF», затем идут 6 блоков данных в конце пакета передается 2-х байтовое поле .В конце идет бит четности (формируется при выполнении операции «Исключающее ИЛИ «четное число логических единиц в байте должно соответствовать биту чётности 0 ) и 2 стоповых бита.
— Длина одного пакета 52 байта.
— Время паузы между двумя пакетами не более 2-х бит.
Характеристики блока данных:
— Длина блока 8 байт.
— Далее 4 значения по 2 байта
Собственно вопрос, который меня интересует: как это все правильно организовать.
Первым делом я кинулся искать как работать с COM-портом, наткнулся на пространство имен:
Ну, и первым делом, как мудрый ВАСЯ, определяю доступные порты в системе и вывожу их в combobox.
Далее, по кнопке старт я запускаю само чтение данных приходящих с устройства. Вот тут для меня начинается дремучий лес.
Теперь вопрос:
Как я понимаю эту проблему — в порт записываются данные, переданные устройством, а мне их необходимо считать.
Как мне правильно дождаться начала пакета «0xFF,0xFF», чтобы после это принимать оставшуюся часть пакета, или прием стоит начать после получения конца предыдущего пакета?
Если у кого-то есть подобные коды обработки ожидания начала или конца пакетов и нет желания объяснять, просьба скинуть участки кода буду разбираться сам.
После правильного начала приема пакетов мне их необходимо рассортировать, ничего лучше чем засунуть принятый пакет в переменную последовательности байт я не нашел, точнее это можно сказать единственное, что улеглось в моей голове.
Но приходит мне какая то ересь.
Правильно ли я понимаю этот процесс чтения из com-порта?
Просьба пнуть, ткнуть, дать леща или помочь в решении данного задания кому как будет угодно.
Кто знает что почитать конкретно по этому вопросу, где доступно написано или есть подобные решения просьба скинуть почитать что бы лучше разбираться.
Пока как то так. Буду признателен любой помощи, подсказке, куску кода.
Перенаправление данных из COM-порта в Web
Недавно на хабре была статья «Отображаем данные из Serial в Chrome Application» о том, как красиво представить данные, отправляемые Arduin-кой в Serial. По-моему, ребята предложили очень красивое решение, которое с одной стороны выглядит достаточно простым, а с другой позволяет получить прекрасный результат с минимумом усилий.
В комментариях к статье было высказано сожаление о том, что такое решение не заработает под Firefox-ом и высказана идея, что «можно еще написать простенький веб-сервер с выдачей html на основе этой штуки». Меня эта идея «зацепила», быстрый поиск в google готового решения не выдал, и я решил реализовать идею сам. И вот, что из этого вышло.
Предупреждение! Предлагаемое решение ни в коем случае нельзя рассматривать как законченное. В отличие от Serial Projector от Амперки — это концепт, демонстрация возможного подхода, работающий прототип и не более того.
Некоторое время назад я делал проект, в котором использовал встроенные в Android-смартфон акселерометры для управления сервами, подключёнными к Arduino. Тогда для этих целей я воспользовался проектами Scripting Layer for Android (SL4A) и RemoteSensors. Оказывается, в стандартную библиотеку python-а входит пакет BaseHTTPServer, с помощью которого поднять веб-сервис на питоне — это задача на пару строчек кода.
Под рукой не было никаких датчиков для Arduino, поэтому в качестве источника отображаемой информации я воспользовался встроенным в Arduino Uno внутренним термометром. Насколько я понимаю, он не очень точный и совсем не предназначен для измерения температуры окружающей среды, но для прототипирования вполне сойдёт.
После недолгого гугления возник вот такой скетч для ардуинки:
Этот скетч открывает COM-порт, настраивает его на скорость 115200 бод и затем каждую секунду пишет в него текущее значение встроенного термометра. (Не спрашивайте меня, в каких единицах выдаётся температура — для описываемой задачи это не важно). Поскольку значение меняется не очень активно, для лучшей видимости изменения данных перед температурой выводится номер строки.
Для проверки того, что веб-сервер будет отдавать наружу только целые строки, а не их части по мере чтения из COM-порта, строка
была заменена на
т.е. сформированная строка выводится в последовательный порт не целиком, а посимвольно, с паузами в 200 мс.
Для начала был написан совсем простенький прототип веб-сервера (ниже он разобран по частям):
Разберём скрипт по частям.
Поскольку это прототип, то все основные параметры работы (имя COM-порта, его скорость, а также номер TCP-порта, на котором будет работать веб-сервер) указаны прямо в исходном тексте:
Разумеется, можно организовать чтение этих параметров из командной строки. Например, с помощью модуля argparse это делается очень быстро, просто и гибко.
В данном же случае пользователям Windows надо в диспетчере устройств узнать имя COM-порта, к которому подключена Arduin-ка. У меня это был ‘COM6’. Пользователям других операционок надо использовать средства своих ОС. У меня совсем нет опыта работы с MacOS и в Linux-е я с COM-портами тоже не работал, но там, скорее всего, это будет что-нибудь типа «/dev/ttySn».
Далее идёт определение глобальной переменной, к которой будет привязан экземпляр класса Serial, отвечающего в питоне за работу с COM-портом:
создаётся веб-сервер, который будет слушать запросы на заданном порту WEB_SERVER_PORT. А обрабатывать эти запросы будет экземпляр класса Handler, описанный ниже.
Следующие строки — это небольшой «хак», позволяющий вывести IP-адрес, на котором собственно работает запущенный веб-сервер:
Насколько я понял, не существует иного способа узнать этот IP. А как без этого знания мы будем обращаться из браузера к нашему серверу?
Поэтому приходится открывать сокет и подключаться к сайту гугла, чтобы из атрибутов уже этого сокета извлечь информацию о собственном IP-адресе.
Чуть ниже происходит открытие COM-порта и собственно запуск веб-сервера:
Затем следует описание класса, который отвечает за обработку полученных запущенным веб-сервером запросов:
Это наследник встроенного в модуль BaseHTTPServer класса, в котором достаточно переопределить только метод do_GET
Поскольку, это ещё пока прототип, то сервер будет «рад» любому запросу — какой бы URL у него не запросили, он будет отдавать клиенту все данные читаемые из COM-порта. Поэтому в Handler.do_GET он сразу отвечает кодом успеха и нужными заголовками:
после чего запускается бесконечный цикл, в котором происходит попытка чтения целой строчки из COM-порта и, если эта попытка оказалась успешной, передача её веб-клиенту:
В проекте, который был взят за основу, этот бесконечный цикл был «завёрнут» в блок try … except, с помощью которого предполагалось аккуратно обрабатывать разрыв соединения. Возможно, в Android-е (базовый проект разрабатывался под него) это и работает нормально, но у меня под Windows XP так не вышло — при разрыве соединения возникало какое-то другое исключение, которое я так и не научился перехватывать. Но, к счастью, это не мешало веб-серверу работать нормально и принимать следующие запросы.
Функция получения целой строки из COM-порта работает по тому же принципу, что и у создателей Serial Projector:
- есть некоторый глобальный буфер, в котором хранится всё, что прочитано из COM-порта
- при каждом обращении к функции она пытается прочитать что-нибудь из COM-порта
- если ей это удаётся, то
- она добавляет только что прочитанное к указанному глобальному буферу
- пытается поделить глобальный буфер максимум на две части символом конца строки
- если и это ей удаётся, то первую часть она возвращает в вызвавшую процедуру, а вторую часть использует в качестве нового значения глобального буфера
- если в COM-порту нет новых данных или не найден символ конца строки, то функция возвращает None:
В результате получилось так:
Видно, что в браузере появляются строчки, читаемые из COM-порта. Я ничего не понимаю в веб-фронтенде: JavaScript, Ajax, CSS и DOM — это для меня тёмный лес. Но мне кажется, что для программистов, создающих веб-интерфейсы, этого должно быть вполне достаточно, чтобы преобразовать этот вывод в такую же красивую картинку, что выдаёт Serial Projector от Амперки. По-моему, задача сводится к тому, чтобы создать javascript-сценарий, который обращается к веб-серверу, читает из него поток и последнюю прочитанную строчку выводит в нужное место веб-страницы.
На всякий случай я решил подстраховаться и попытался сделать первое приближение своими силами. Не очень глубокий поиск в гугле подсказал, что вообще-то для таких целей, по крайней мере, раньше использовали WebSockets или Server-Sent Events. Я нашел, как мне показалось, неплохой учебник по использованию Server-Sent Events и решил использовать эту технологию.
Примечание! Похоже, это не самое лучшее решение, потому что эта технология не заработала ни в Internet Explorer 8, ни в браузере, встроенном в Android 2.3.5. Но она заработала хотя бы в Firefox 39.0, поэтому я не стал «копать» дальше.
Почитав указанный учебник, а также ещё один на русском языке, я взял за основу проект simpl.info/eventsource.
С точки зрения питоновского скрипта изменения под Server-Sent Events совершенно незначительные:
- надо заменить тип отдаваемых клиенту данных:
строчку
а также перед прочитанной из COM-порта строчкой вставить префикс «data: » и добавить ещё один символ перевода строки:
строки
Всё остальное могло бы, наверное, остаться без изменений, но…
Сперва я создал файл index.html вот такого содержания:
Самые интересные в нём — это строка
которая формирует место для вывода очередной строчки из COM-порта, и javascript-сценарий
который собственно и занимается чтением потока из веб-сервера и выводом прочитанной информации в указанное место.
Я предполагал открывать этот файл в браузере, например, с диска или с какого-нибудь другого веб-сервера, но это не сработало: при открытии страницы с диска javascript-сценарий однократно обращался к запущенному питоновскому веб-серверу и тут же разрывал соединение. Я не понял, почему так происходит, и предположил, что это, возможно, какое-то проявление защиты браузера от различных атак. Наверное, ему не нравится, что сама страница открыта с одного источника, а сценарий считывает данные из другого источника.
Поэтому было принято решение поменять питоновский веб-сервер так, чтобы он отдавал и эту html-страницу. Тогда бы получилось, что и страница, и поток считываются из одного источника. Не знаю, то ли моё предположение насчёт безопасности оказалось верным, то ли ещё что, но при такой реализации всё заработало как надо.
Поменять, разумеется, надо только класс-обработчик запросов Handler:
В данном варианте предполагается, что веб-сервер будет отвечать только на два запроса: ‘/index.html’ (отдавая html-код страницы) и ‘/get_serial’ (отдавая бесконечный поток строк, считываемых из COM-порта). На все остальные запросы он будет отвечать кодом 404.
Поскольку index.html отдаётся питоновским веб-сервером, то его можно слегка изменить, указав вместо абсолютного адреса потока строк из COM-порта относительный:
строку
В итоге получилось вот так:
На этом я решил остановиться. Как мне кажется, оформить страницу красиво — это уже должно быть совсем просто. Но я не владею ни HTML, ни CSS, поэтому пусть это сделает кто-нибудь другой. Я видел свою задачу в том, чтобы показать, что сделать веб-сервис, отдающий данные из COM-порта, вроде бы, совсем не сложно.
Все исходники можно взять на гитхабе.
Ещё раз повторюсь: представленный код — это не законченное решение, которое можно «пускать в продакшен». Это только прототип, который показывает принципиальный подход к решению задачи.
Над чем тут ещё можно поработать:
- во-первых, чтение данных из COM-порта в питоновском скрипте сделано очень «топорно» — по сути дела происходит постоянный поллинг «а нет ли чего свеженького?». Такой подход, разумеется, нагружает процессор и одно ядро на моём компьютере занято на 100%.
В качестве решения можно использовать блокирующее чтение с таймаутом. Для этого достаточно при открытии COM-порта качестве таймаута указать ненулевое значение (в секундах), например: