#include "pch.h"
#include "IRPS.h"

#include <conio.h>
#include "Platform.h"
#include "BKMessageBox.h"

// Проблема 1: приём с консоли вообще никак не работает, код ещё не написан.
//
// Проблема 2: приём с ком порта работает только для одиночных символов, пакетная передача не работает
//          потому что происходит потеря байтов.
// решения я тупо не вижу. Как это сделано у других людей - без понятия.

#pragma warning(disable:4996)
#define SAVE_LOG 0
#if (_DEBUG && SAVE_LOG)
FILE *SendLog = nullptr;
FILE *RecvLog = nullptr;
#endif



IMPLEMENT_DYNAMIC(CIRPS, CBKMPIDevice)

CIRPS::CIRPS()
	: m_bRX_ParityCheck(true)
	, m_bRX_PEV(true)
	, m_hPort(INVALID_HANDLE_VALUE)
	, m_bStopTXThread(false)
	, m_bTXTrigger(false)
	, m_bStopRXThread(false)
	, m_hWaitableTimer(nullptr)
{
#if (_DEBUG && SAVE_LOG)
	SendLog = fopen("_send.log", "wb");
	fclose(SendLog);
	RecvLog = fopen("_recv.log", "wb");
	fclose(RecvLog);
#endif
	Configure(static_cast<IRPS_MODE>(g_Config.m_nTTYMode));
	m_hPort = OpenPort(g_Config.m_strMapCOMPort);
	CreateTimer();
	StartThreads();
}

CIRPS::~CIRPS()
{
	StopThreads();
	DeleteTimer();
	ClosePort(m_hPort);
}

bool CIRPS::InitialSetDevice()
{
	m_nMemorySize = 0;              // памяти нет
	InitMemory(BK_PORTSIO_AREA);    // Инициализация модуля памяти
	return true;
}

void CIRPS::Reconfigure()
{
	StopThreads();
	ClosePort(m_hPort); // кстати - это не закрывает порт на самом деле, и чтобы переконфигурация работала, нужно полностью перезапускать эмулятор.
	Reset();
	Configure(static_cast<IRPS_MODE>(g_Config.m_nTTYMode));
	m_hPort = OpenPort(g_Config.m_strMapCOMPort);
	StartThreads();
}

// конфигурирование адресов портов и векторов прерывания
// реализовано только внутреннее. задание внешних адресов - игнорируется
void CIRPS::Configure(const IRPS_MODE nMode)
{
	uint16_t addr, vec;

	switch (nMode)
	{
		default:
		case IRPS_MODE::MODE_V060:
			addr = 0177560;
			vec = 060;
			break;

		case IRPS_MODE::MODE_V360:
			addr = 0176560;
			vec = 0360;
			break;

		case IRPS_MODE::MODE_V370:
			addr = 0176570;
			vec = 0370;
			break;

		case IRPS_MODE::MODE_SMKC: // спец конфигурация для совместимости с СМК-512
			addr = 0177560;
			vec = 0360;
			break;
	}

	RX_CSR = addr;
	RX_DAT = addr + 2;
	TX_CSR = addr + 4;
	TX_DAT = addr + 6;
	RX_VEC = vec;
	TX_VEC = vec + 4;

	// выставим настройки проверки паритета и чётности/нечётности
	switch (g_Config.m_nTTYParity)
	{
		default:
			g_Config.m_nTTYParity = NOPARITY; // все остальные режимы не обрабатываются и
			[[fallthrough]];

		case NOPARITY: // без паритета
			m_bRX_ParityCheck = false; // паритет не проверять
			m_bRX_PEV = false; // не имеет значения
			break;

		case ODDPARITY: // нечётные биты
			m_bRX_ParityCheck = true; // паритет проверять
			m_bRX_PEV = false; // по нечётным битам
			break;

		case EVENPARITY: // чётные биты
			m_bRX_ParityCheck = true; // паритет проверять
			m_bRX_PEV = true; // по чётным битам
			break;
	}

	CalcDelay();
}

void CIRPS::CalcDelay()
{
	// рассчитаем временные задержки
	double clk_in = 4608000.0; // Гц

	if ((g_Config.m_nTTYSpeed == 115200) || (g_Config.m_nTTYSpeed == 38400))
	{
		clk_in *= 2;
	}

	double baud_time;

	switch (g_Config.m_nTTYSpeed)
	{
		case 115200:
		case 57600:
			baud_time = 5.0 / clk_in;
			break;

		case 38400:
		case 19200:
			baud_time = 15.0 / clk_in;
			break;

		default:
		case 9600:
			baud_time = 30.0 / clk_in;
			break;

		case 4800:
			baud_time = 60.0 / clk_in;
			break;

		case 2400:
			baud_time = 120.0 / clk_in;
			break;
	}

	// длина посылки в битах
	int len = g_Config.m_nTTYByteSize + ((g_Config.m_nTTYParity != NOPARITY) ? 1 : 0);
	// добавляем стартовый бит
	len += 2;

	// добавляем стоповый бит
	switch (g_Config.m_nTTYStopBits)
	{
		case ONESTOPBIT:
			len += 2;
			break;

		case ONE5STOPBITS:
			len += 3;
			break;

		case TWOSTOPBITS:
			len += 4;
			break;
	}

	double datatime = len * baud_time; // сек
	// ещё надо прибавить время на формирование сигналов в портах
	datatime += 32.0 / clk_in;
	// задержка для таймера - 100 нсек интервал
	m_liDelayMKS.QuadPart = -10LL * LONGLONG(datatime * 1000000.0 + 0.5);
}

// Эта хрень как-то работает примерно, потому что нет никакого реального времени
void CIRPS::MakeDelay()
{
	if (m_mutSafeDelay.try_lock())
	{
		::SetWaitableTimer(m_hWaitableTimer, &m_liDelayMKS, 0, nullptr, nullptr, FALSE); // однократный
		// ждём срабатывания таймера.
		::WaitForSingleObject(m_hWaitableTimer, INFINITE);
		m_mutSafeDelay.unlock();
	}
}

bool CIRPS::CreateTimer()
{
	m_hWaitableTimer = ::CreateWaitableTimer(nullptr, FALSE, nullptr); // самосбрасывающийся
	return (m_hWaitableTimer != nullptr);
}

void CIRPS::DeleteTimer()
{
	if (m_hWaitableTimer)
	{
		::CancelWaitableTimer(m_hWaitableTimer);
		::CloseHandle(m_hWaitableTimer);
		m_hWaitableTimer = nullptr;
	}
}



void CIRPS::OnReset()
{
	m_chTXData = 0;
	m_wTXcsr.setValue(TTY_TX_RDY);
	m_chRXData = 0;
	m_wRXcsr.setValue(0);

	if (m_hPort != INVALID_HANDLE_VALUE)
	{
		::FlushFileBuffers(m_hPort);
		::PurgeComm(m_hPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
	}
}

bool CIRPS::GetByteT(const uint16_t addr, uint8_t *pValue, int &nTC)
{
	uint16_t w;

	if (GetWordT(addr, &w, nTC))
	{
		if (addr & 1)
		{
			w >>= 8;
		}

		*pValue = LOBYTE(w);
		return true;
	}

	*pValue = 0;
	return false;
}

bool CIRPS::GetWordT(const uint16_t addr, uint16_t *pValue, int &nTC)
{
	const uint16_t a = addr & 0177776;
	nTC = REG_TIMING_CORR_VALUE;

	if (a == RX_CSR)    // чтение регистра состояния приёмника
	{
		*pValue = m_wRXcsr.getValue();
		return true;
	}

	if (a == RX_DAT)    // чтение регистра данных приёмника
	{
		*pValue = m_chRXData;   // получаем текущие данные
		// говорим, что можно присылать следующие данные
		m_wRXcsr.clrBit(TTY_RX_PER | TTY_RX_OVRFL | TTY_RX_RDY);
		::EscapeCommFunction(m_hPort, SETDTR);
		return true;
	}

	if (a == TX_CSR)    // чтение регистра состояния передатчика
	{
		*pValue = m_wTXcsr.getValue();
		return true;
	}

	if (a == TX_DAT)    // чтение регистра состояния передатчика
	{
		*pValue = RX_VEC;   // возвращает адрес вектора
		return true;
	}

	nTC = 0;        // если не попали в порты - то корректируем тайминг
	*pValue = 0;    // и возвращаем ничего
	return false;
}


bool CIRPS::GetByteIndirect(const uint16_t addr, uint8_t *pValue)
{
	uint16_t w;

	if (GetWordIndirect(addr, &w))
	{
		if (addr & 1)
		{
			w >>= 8;
		}

		*pValue = LOBYTE(w);
		return true;
	}

	*pValue = 0;
	return false;
}

// почти копипаста обычной функции, но чтение RX_DAT - просто чтение текущего значения
bool CIRPS::GetWordIndirect(const uint16_t addr, uint16_t *pValue)
{
	const uint16_t a = addr & 0177776;

	if (a == RX_CSR)
	{
		*pValue = m_wRXcsr.getValue();
		return true;
	}

	if (a == RX_DAT)
	{
		*pValue = m_chRXData;   // получаем текущие данные
		return true;
	}

	if (a == TX_CSR)
	{
		*pValue = m_wTXcsr.getValue();
		return true;
	}

	if (a == TX_DAT)
	{
		*pValue = RX_VEC;
		return true;
	}

	*pValue = 0;
	return false;
}


bool CIRPS::SetByteT(const uint16_t addr, uint8_t value, int &nTC)
{
	auto w = static_cast<uint16_t>(value);

	if (addr & 1)
	{
		w <<= 8;
	}

	return SetWordT(addr, w, nTC);
}

bool CIRPS::SetWordT(const uint16_t addr, uint16_t value, int &nTC)
{
	const uint16_t a = addr & 0177776;
	nTC = REG_TIMING_CORR_VALUE;

	if (a == RX_CSR) // запись в порт состояния приёмника
	{
		const uint16_t nRX_IEn_prev = m_wRXcsr.getBit(TTY_RX_INT);   // запомним состояние флага прерывания
		m_wRXcsr.setBit(TTY_RX_INT, value); // установим новое состояние флага

		// если данные готовы и бит разрешения прерывания взводится - то генерируем прерывание
		if ((m_wRXcsr.getBit(TTY_RX_RDY | TTY_RX_INT) == (TTY_RX_RDY | TTY_RX_INT)) && !nRX_IEn_prev)
		{
			g_Platform.VectorInterrupt(RX_VEC); // генерируем прерывание
		}

		return true;
	}

	if (a == RX_DAT)
	{
		// запись игнорируется
		return true;
	}

	if (a == TX_CSR)    // запись в порт состояния передатчика
	{
		// устанавливаем состояния
		const uint16_t nTX_IEn_prev = m_wTXcsr.getBit(TTY_TX_INT); //запомним состояния флага прерывания
		m_wTXcsr.setBit((TTY_TX_INT | TTY_TX_LOOP | TTY_TX_BRK), value);

		if (m_wTXcsr.getBit(TTY_TX_BRK) && (m_hPort != INVALID_HANDLE_VALUE))
		{
			::EscapeCommFunction(m_hPort, SETBREAK);
		}

		// если данные готовы и бит взводится - то генерируем прерывание
		if ((m_wTXcsr.getBit(TTY_TX_RDY | TTY_TX_INT) == (TTY_TX_RDY | TTY_TX_INT)) && !nTX_IEn_prev)
		{
			g_Platform.VectorInterrupt(TX_VEC); // генерируем прерывание
		}

		return true;
	}

	if (a == TX_DAT)
	{
		m_wTXcsr.clrBit(TTY_TX_RDY);
		m_chTXData = LOBYTE(value);
		TXFunc();
		return true;
	}

	nTC = 0;
	return false;
}





// получим извне данные для входного регистра ирпс
void CIRPS::ReceiveData(const uint8_t data)
{
	// если установлен бит 2 TX_CSR, то приём извне игнорируется
	if (!m_wTXcsr.getBit(TTY_TX_LOOP))
	{
		// а если нет - то данные принимаются.
		_int_ReceiveData(data);

		if (m_wRXcsr.getBit(TTY_RX_INT))
		{
			g_Platform.VectorInterrupt(RX_VEC);
		}
	}
}

void CIRPS::_int_ReceiveData(const uint8_t data)
{
	if (m_wRXcsr.getBit(TTY_RX_RDY)) // если пришла посылка, а мы старую не прочитали
	{
		m_wRXcsr.setBit(TTY_RX_OVRFL); // установим бит переполнения
	}

	m_chRXData = data;
	m_wRXcsr.setBit(TTY_RX_RDY);
	// проверка чётности
	uint8_t td = data;
	bool bPar = false;

	for (int i = 8; i > 0; i--)
	{
		if (td & 1)
		{
			bPar = !bPar;
		}

		td >>= 1;
	}

	if (m_bRX_ParityCheck && (m_bRX_PEV ^ bPar))
	{
		m_wRXcsr.setBit(TTY_RX_PER);
	}
	else
	{
		m_wRXcsr.clrBit(TTY_RX_PER);
	}
}


void CIRPS::RXThreadFunc()
{
	TRACE("RX thread started\n");
	std::lock_guard<std::mutex> lk(m_mutRXThreadWork); // устанавливаем событие работы потока

	for (;;)
	{
		std::lock_guard<std::mutex> lk(m_RXmutex); // блокируем

		if (m_bStopRXThread)   // если надо выходить
		{
			break;  // выходим
		}

		if (m_hPort == INVALID_HANDLE_VALUE)
		{
			// TODO: теоретически приём данных из консоли
			m_chRXData = 0;
			MakeDelay();
		}
		else
		{
			// приём данных из ком порта
			uint8_t data = 0;

			// никак не удаётся победить потерю данных. поэтому будем мспользовать такой хак:
			// пока бит готовности не сбросить, т.е. пока текущие данные из порта не прочитать,
			// новые данные тупо принимать не будем.
			// это неправильно, таким образом у нас никогда не будет переполнения.
			// и получается плевать на скорости и согласование.
			// но зато это работает.

			if (!m_wRXcsr.getBit(TTY_RX_RDY) && ByteIn(&data))
			{
#if (_DEBUG && SAVE_LOG)
				{
					// выводим принятое в лог
					RecvLog = fopen("_recv.log", "a");
					fwrite(&data, 1, 1, RecvLog);
					fclose(RecvLog);
				}
#endif
				::EscapeCommFunction(m_hPort, CLRDTR);

				//          TRACE("received (%03d)\n", data);
				if (m_wRXcsr.getBit(TTY_RX_RDY)) // если пришла посылка, а мы старую не прочитали
				{
					m_wRXcsr.setBit(TTY_RX_OVRFL); // установим бит переполнения
				}

				// передаём прочитанный символ
				// ставим флаг, что данные готовы
				m_chRXData = data, m_wRXcsr.setBit(TTY_RX_RDY);

				if (m_wRXcsr.getBit(TTY_RX_INT)) // если прерывания разрешены
				{
					g_Platform.VectorInterrupt(RX_VEC); // генерируем прерывание
				}

				MakeDelay();
			}
		}
	}

	TRACE("RX thread finished\n");
}



void CIRPS::TXFunc()
{
	{
		std::lock_guard<std::mutex> lk(m_TXmutex);
		m_bTXTrigger = true;
	}
	m_TXcv.notify_one();   // трогаем поток
}

// поток для вывода данных в консоль или в порт
void CIRPS::TXThreadFunc()
{
	TRACE("TX thread started\n");
	std::lock_guard<std::mutex> lk(m_mutTXThreadWork); // устанавливаем событие работы потока

	for (;;)
	{
		std::unique_lock<std::mutex> lk(m_TXmutex); // блокируем
		m_TXcv.wait(lk, [&]()  // ждём события
		{
			return m_bTXTrigger;
		});
		m_bTXTrigger = false;

		if (m_bStopTXThread)   // если надо выходить
		{
			break;  // выходим
		}

		if (m_wTXcsr.getBit(TTY_TX_LOOP))
		{
			_int_ReceiveData(m_chTXData);
		}
		else
		{
			if (m_hPort == INVALID_HANDLE_VALUE)
			{
				// вывод данных в консоль
				COutputView *out = g_Platform.GetMainPtr()->GetOutputWndPtr();
				const TCHAR ch = (m_chTXData < 32) ? m_chTXData : Global::BKToWIDEChar(m_chTXData); // преобразуем кои8 в юникодный символ
				out->SendChar(ch); // выводим в консоль
			}
			else
			{
				bool bres = ByteOut(m_chTXData);
#if (_DEBUG && SAVE_LOG)
				{
					SendLog = fopen("_send.log", "a");
					fwrite(&m_chTXData, 1, 1, SendLog);
					fclose(SendLog);
				}
#endif
			}
		}

		m_wTXcsr.setBit(TTY_TX_RDY);

		if (m_wTXcsr.getBit(TTY_TX_INT))
		{
			g_Platform.VectorInterrupt(TX_VEC);
		}

		MakeDelay();
	}

	TRACE("TX thread finished\n");
}



bool CIRPS::StartThreads()
{
	m_TXthr = std::thread(&CIRPS::TXThreadFunc, this);
	m_TXthr.detach();
	m_RXthr = std::thread(&CIRPS::RXThreadFunc, this);
	m_RXthr.detach();
	return true;
}

void CIRPS::StopThreads()
{
	if (!m_mutRXThreadWork.try_lock()) // если поток работает
	{
		std::lock_guard<std::mutex> lks(m_mutSafeDelay);
		// подаём команду на останов потока
		{
			std::lock_guard<std::mutex> lk(m_RXmutex);
			m_bStopRXThread = true;    // взводим флаг остановки потока
		}
		std::lock_guard<std::mutex> lk(m_mutRXThreadWork); // ждём завершения потока
		m_bStopRXThread = false;
	}
	else
	{
		m_mutRXThreadWork.unlock();
	}

	if (!m_mutTXThreadWork.try_lock()) // если поток работает
	{
		std::lock_guard<std::mutex> lks(m_mutSafeDelay);
		// подаём команду на останов потока
		{
			std::lock_guard<std::mutex> lk(m_TXmutex);
			m_bTXTrigger = true;
			m_bStopTXThread = true;    // взводим флаг остановки потока
		}
		m_TXcv.notify_one();
		std::lock_guard<std::mutex> lk(m_mutTXThreadWork); // ждём завершения потока
		m_bTXTrigger = false;
		m_bStopTXThread = false;
	}
	else
	{
		m_mutTXThreadWork.unlock();
	}
}

// открываем нужный нам порт.
// выход - хэндл файла открытого порта
HANDLE CIRPS::OpenPort(const CString &strCOM)
{
	// После того, как имя COM порта получено, дальнейшая работа с ним происходит как с файлом.
	// Но для начала нам надо его настроить
	HANDLE hPort;   // Дескриптор COM-порта

	if (strCOM == g_ConsoleName) // если мы мапимся в консоль
	{
		hPort = INVALID_HANDLE_VALUE; // то хэндл вот такой
		return hPort; // и выходим, нечего тут делать
	}

	// Открываем COM-порт
	hPort = ::CreateFile((_T("\\\\.\\") + strCOM).GetString(), // Имя COM-порта
	                     GENERIC_READ | GENERIC_WRITE,
	                     0,
	                     nullptr,
	                     OPEN_EXISTING,
	                     FILE_ATTRIBUTE_NORMAL,
	                     nullptr);

	if (hPort == INVALID_HANDLE_VALUE)
	{
		const DWORD dwError = ::GetLastError();
		CString str;
		str.Format(_T("Невозможно открыть последовательный порт (0x%08lx)."), dwError);
		g_BKMsgBox.Show(str, MB_OK);
		return hPort;
	}

	// Получаем состояние управляющей структуры COM-порта,
	// если не удалось выводим сообщение об ошибке и выходим из
	// обработчика
	COMMCONFIG comm;
	memset(&comm, 0, sizeof(COMMCONFIG));
	comm.dwSize = sizeof(COMMCONFIG);

	if (!::GetCommState(hPort, &comm.dcb))
	{
		const DWORD dwError = ::GetLastError();
		CString str;
		str.Format(_T("Невозможно получить состояние управляющей структуры порта (0x%08lx)."), dwError);
		g_BKMsgBox.Show(str, MB_OK);
		ClosePort(hPort);
		return hPort;
	}

	// Настраиваем управляющую структуру COM-порта
	comm.dcb.fBinary    = TRUE;                     //включаем двоичный режим обмена
	comm.dcb.BaudRate   = g_Config.m_nTTYSpeed;
	comm.dcb.ByteSize   = g_Config.m_nTTYByteSize;  // Размер байта
	comm.dcb.Parity     = g_Config.m_nTTYParity;    // Паритет
	comm.dcb.StopBits   = g_Config.m_nTTYStopBits;  // количество стоповых бита

	switch (g_Config.m_nCOMHSMode)
	{
		default:
		case 0: // нет
			comm.dcb.fOutxCtsFlow = FALSE;                  // Disable CTS monitoring
			comm.dcb.fOutxDsrFlow = FALSE;                  // Disable DSR monitoring
			comm.dcb.fDtrControl = DTR_CONTROL_DISABLE;     // Disable DTR monitoring
			comm.dcb.fOutX = FALSE;                         // Disable XON/XOFF for transmission
			comm.dcb.fInX = FALSE;                          // Disable XON/XOFF for receiving
			comm.dcb.fRtsControl = RTS_CONTROL_DISABLE;     // Disable RTS (Ready To Send)
			break;

		case 1: // да
			comm.dcb.fOutxCtsFlow = FALSE;                  // Disable CTS monitoring
			comm.dcb.fOutxDsrFlow = FALSE;                  // Disable DSR monitoring
			comm.dcb.fDtrControl = DTR_CONTROL_ENABLE;      // Enable DTR monitoring
			comm.dcb.fOutX = FALSE;                         // Disable XON/XOFF for transmission
			comm.dcb.fInX = FALSE;                          // Disable XON/XOFF for receiving
			comm.dcb.fRtsControl = RTS_CONTROL_ENABLE;      // Enable RTS (Ready To Send)
			break;

		case 2: //xon/xoff
			comm.dcb.fOutxCtsFlow = FALSE;                  // Disable CTS (Clear To Send)
			comm.dcb.fOutxDsrFlow = FALSE;                  // Disable DSR (Data Set Ready)
			comm.dcb.fDtrControl = DTR_CONTROL_DISABLE;     // Disable DTR (Data Terminal Ready)
			comm.dcb.fOutX = TRUE;                          // Enable XON/XOFF for transmission
			comm.dcb.fInX = TRUE;                           // Enable XON/XOFF for receiving
			comm.dcb.fRtsControl = RTS_CONTROL_DISABLE;     // Disable RTS (Ready To Send)
			break;

		case 3: //аппаратно
			comm.dcb.fOutxCtsFlow = TRUE;                   // Enable CTS monitoring
			comm.dcb.fOutxDsrFlow = TRUE;                   // Enable DSR monitoring
			comm.dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;   // Enable DTR handshaking
			comm.dcb.fOutX = FALSE;                         // Disable XON/XOFF for transmission
			comm.dcb.fInX = FALSE;                          // Disable XON/XOFF for receiving
			comm.dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;   // Enable RTS handshaking
			break;
	}

	// Можно также воспользоваться стандартным диалоговым
	// окном настройки управляющей структуры COM-порта:
	// CommConfigDialog(strCOM.GetString(), nullptr, &comm);

	// Применяем настроенную структуру к COM-порту, если не удалось выводим сообщение об ошибке
	if (!::SetCommState(hPort, &comm.dcb))
	{
		const DWORD dwError = ::GetLastError();
		CString str;
		str.Format(_T("Невозможно сконфигурировать последовательный порт (0x%08lx)."), dwError);
		g_BKMsgBox.Show(str, MB_OK);
		ClosePort(hPort);
		return hPort;
	}

	// Получаем текущие настройки тайм-аутов COM-порта
	COMMTIMEOUTS commTimeouts;

	if (!::GetCommTimeouts(hPort, &commTimeouts))
	{
		const DWORD dwError = ::GetLastError();
		CString str;
		str.Format(_T("Невозможно получить настройки тайм-аутов порта (0x%08lx)."), dwError);
		g_BKMsgBox.Show(str, MB_OK);
		ClosePort(hPort);
		return hPort;
	}

	// Перенастраиваем тайм-ауты:
	// Максимальный интервал чтения в миллисекундах между
	// двумя принимаемыми символами
	commTimeouts.ReadIntervalTimeout = 100; // MAXDWORD;
	// Константа в миллисекундах используемая для вычисления
	// полного тайм-аута операции чтения
	commTimeouts.ReadTotalTimeoutConstant = 0;
	// Множитель используемый для вычисления полного тайм-аута
	// операции чтения в миллисекундах
	commTimeouts.ReadTotalTimeoutMultiplier = 1;
	// Полный максимальный тайм-аут операции чтения
	// вычисляется следующим образом // ReadTotalTimeoutConstant + (ReadIntervalTimeout * количество считываемых байт)
	commTimeouts.WriteTotalTimeoutConstant = 100;

	// Устанавливаем тайм-ауты для COM-порта
	if (!::SetCommTimeouts(hPort, &commTimeouts))
	{
		const DWORD dwError = ::GetLastError();
		CString str;
		str.Format(_T("Невозможно настроить тайм-ауты последовательного порта (0x%08lx)."), dwError);
		g_BKMsgBox.Show(str, MB_OK);
		ClosePort(hPort);
		return hPort;
	}

	::FlushFileBuffers(hPort);
	::PurgeComm(hPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
	//::SetupComm(Port, 512, 512);
	//::SetupComm(Port, 1, 1);
	return hPort;
}

bool CIRPS::ByteIn(uint8_t *data)
{
	DWORD dwReaded;
	const bool result = !!::ReadFile(m_hPort, data, 1, &dwReaded, nullptr); // читаем байт
	return result && (dwReaded == 1);
}

bool CIRPS::ByteOut(const uint8_t data)
{
	DWORD dwWritten;
	const bool result = !!::WriteFile(m_hPort, &data, 1, &dwWritten, nullptr); // пишем байт
	return result && (dwWritten == 1);
}

void CIRPS::ClosePort(HANDLE &hPort)
{
	// И закрываем порт после работы :
	if (hPort != INVALID_HANDLE_VALUE)
	{
		// Если порт открыт
		::FlushFileBuffers(hPort);
		::PurgeComm(hPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
		::CancelIo(hPort);
		::CloseHandle(hPort);
		hPort = INVALID_HANDLE_VALUE;
	}
}

