411 lines
11 KiB
C++
411 lines
11 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// SerialControllerImpl.cpp
|
|
//
|
|
// Windows Implementation of the cross-platform serial port
|
|
//
|
|
// Copyright (c) 2010 Jason Frazier <frazierjason@gmail.com>
|
|
//
|
|
// SOFTWARE NOTICE AND LICENSE
|
|
//
|
|
// This file is part of OpenZWave.
|
|
//
|
|
// OpenZWave is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License,
|
|
// or (at your option) any later version.
|
|
//
|
|
// OpenZWave is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with OpenZWave. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "../../Defs.h"
|
|
#include "SerialControllerImpl.h"
|
|
|
|
#include "../Log.h"
|
|
|
|
using namespace OpenZWave;
|
|
|
|
DWORD WINAPI SerialReadThreadEntryPoint( void* _context );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::SerialControllerImpl>
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
SerialControllerImpl::SerialControllerImpl
|
|
(
|
|
SerialController* _owner
|
|
):
|
|
m_owner( _owner )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::~SerialControllerImpl>
|
|
// Destructor
|
|
//-----------------------------------------------------------------------------
|
|
SerialControllerImpl::~SerialControllerImpl
|
|
(
|
|
)
|
|
{
|
|
CloseHandle( m_hSerialController );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::Open>
|
|
// Open the serial port
|
|
//-----------------------------------------------------------------------------
|
|
bool SerialControllerImpl::Open
|
|
(
|
|
)
|
|
{
|
|
// Try to init the serial port
|
|
if( !Init( 1 ) )
|
|
{
|
|
// Failed. We bail to allow the app a chance to take over, rather than retry
|
|
// automatically. Automatic retries only occur after a successful init.
|
|
return false;
|
|
}
|
|
|
|
// Create an event to trigger exiting the read thread
|
|
m_hExit = ::CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
|
|
// Start the read thread
|
|
m_hThread = ::CreateThread( NULL, 0, SerialReadThreadEntryPoint, this, CREATE_SUSPENDED, NULL );
|
|
::ResumeThread( m_hThread );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::Close>
|
|
// Close the serial port
|
|
//-----------------------------------------------------------------------------
|
|
void SerialControllerImpl::Close
|
|
(
|
|
)
|
|
{
|
|
::SetEvent( m_hExit );
|
|
::WaitForSingleObject( m_hThread, INFINITE );
|
|
|
|
CloseHandle( m_hThread );
|
|
m_hThread = INVALID_HANDLE_VALUE;
|
|
|
|
CloseHandle( m_hExit );
|
|
m_hExit = INVALID_HANDLE_VALUE;
|
|
|
|
CloseHandle( m_hSerialController );
|
|
m_hSerialController = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialReadThreadEntryPoint>
|
|
// Entry point of the thread for receiving data from the serial port
|
|
//-----------------------------------------------------------------------------
|
|
DWORD WINAPI SerialReadThreadEntryPoint
|
|
(
|
|
void* _context
|
|
)
|
|
{
|
|
SerialControllerImpl* impl = (SerialControllerImpl*)_context;
|
|
if( impl )
|
|
{
|
|
impl->ReadThreadProc();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::ReadThreadProc>
|
|
// Handle receiving data
|
|
//-----------------------------------------------------------------------------
|
|
void SerialControllerImpl::ReadThreadProc
|
|
(
|
|
)
|
|
{
|
|
uint32 attempts = 0;
|
|
while( true )
|
|
{
|
|
// Init must have been called successfully during Open, so we
|
|
// don't do it again until the end of the loop
|
|
if( INVALID_HANDLE_VALUE != m_hSerialController )
|
|
{
|
|
// Enter read loop. Call will only return if
|
|
// an exit is requested or an error occurs
|
|
Read();
|
|
|
|
// Reset the attempts, so we get a rapid retry for temporary errors
|
|
attempts = 0;
|
|
}
|
|
|
|
if( attempts < 25 )
|
|
{
|
|
// Retry every 5 seconds for the first two minutes...
|
|
if( WAIT_OBJECT_0 == ::WaitForSingleObject( m_hExit, 5000 ) )
|
|
{
|
|
// Exit signalled.
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ...retry every 30 seconds after that
|
|
if( WAIT_OBJECT_0 == ::WaitForSingleObject( m_hExit, 30000 ) )
|
|
{
|
|
// Exit signalled.
|
|
break;
|
|
}
|
|
}
|
|
|
|
Init( ++attempts );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::Init>
|
|
// Initialize the serial port
|
|
//-----------------------------------------------------------------------------
|
|
bool SerialControllerImpl::Init
|
|
(
|
|
uint32 const _attempts
|
|
)
|
|
{
|
|
Log::Write( LogLevel_Info, " Trying to open serial port %s (Attempt %d)", m_owner->m_serialControllerName.c_str(), _attempts );
|
|
|
|
m_hSerialController = CreateFileA( m_owner->m_serialControllerName.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL );
|
|
|
|
if( INVALID_HANDLE_VALUE == m_hSerialController )
|
|
{
|
|
//Error
|
|
Log::Write( LogLevel_Error, "ERROR: Cannot open serial port %s. Error code %d\n", m_owner->m_serialControllerName.c_str(), GetLastError() );
|
|
goto SerialOpenFailure;
|
|
}
|
|
|
|
// Configure the serial device parameters
|
|
// Build on the current configuration
|
|
DCB dcb;
|
|
if( !GetCommState( m_hSerialController, &dcb ) )
|
|
{
|
|
//Error. Clean up and exit
|
|
Log::Write( LogLevel_Error, "ERROR: Failed to read serial port state" );
|
|
goto SerialOpenFailure;
|
|
}
|
|
|
|
// Fill in the Device Control Block
|
|
dcb.BaudRate = (DWORD)m_owner->m_baud;
|
|
dcb.ByteSize = 8;
|
|
dcb.Parity = (BYTE)m_owner->m_parity;
|
|
dcb.StopBits = (BYTE)m_owner->m_stopBits;
|
|
|
|
if( !SetCommState( m_hSerialController, &dcb) )
|
|
{
|
|
//Error. Clean up and exit
|
|
Log::Write( LogLevel_Error, "ERROR: Failed to set serial port state" );
|
|
goto SerialOpenFailure;
|
|
}
|
|
|
|
// Set the timeouts for the serial port
|
|
COMMTIMEOUTS commTimeouts;
|
|
commTimeouts.ReadIntervalTimeout = MAXDWORD;
|
|
commTimeouts.ReadTotalTimeoutConstant = 0;
|
|
commTimeouts.ReadTotalTimeoutMultiplier = 0;
|
|
commTimeouts.WriteTotalTimeoutConstant = 0;
|
|
commTimeouts.WriteTotalTimeoutMultiplier = 0;
|
|
if( !SetCommTimeouts( m_hSerialController, &commTimeouts ) )
|
|
{
|
|
// Error. Clean up and exit
|
|
Log::Write( LogLevel_Error, "ERROR: Failed to set serial port timeouts" );
|
|
goto SerialOpenFailure;
|
|
}
|
|
|
|
// Set the serial port to signal when data is received
|
|
if( !SetCommMask( m_hSerialController, EV_RXCHAR ) )
|
|
{
|
|
//Error. Clean up and exit
|
|
Log::Write( LogLevel_Info, "ERROR: Failed to set serial port mask" );
|
|
goto SerialOpenFailure;
|
|
}
|
|
|
|
// Clear any residual data from the serial port
|
|
PurgeComm( m_hSerialController, PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_TXCLEAR );
|
|
|
|
// Open successful
|
|
Log::Write( LogLevel_Info, " Serial port %s opened (attempt %d)", m_owner->m_serialControllerName.c_str(), _attempts );
|
|
return true;
|
|
|
|
SerialOpenFailure:
|
|
Log::Write( LogLevel_Info, "ERROR: Failed to open serial port %s (attempt %d)", m_owner->m_serialControllerName.c_str(), _attempts );
|
|
CloseHandle( m_hSerialController );
|
|
m_hSerialController = INVALID_HANDLE_VALUE;
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::Read>
|
|
// Read data from the serial port
|
|
//-----------------------------------------------------------------------------
|
|
void SerialControllerImpl::Read
|
|
(
|
|
)
|
|
{
|
|
uint8 buffer[256];
|
|
|
|
OVERLAPPED overlapped;
|
|
memset( &overlapped, 0, sizeof(overlapped) );
|
|
overlapped.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
|
|
while( true )
|
|
{
|
|
// Try to read all available data from the serial port
|
|
DWORD bytesRead = 0;
|
|
do
|
|
{
|
|
if( ::ReadFile( m_hSerialController, buffer, 256, NULL, &overlapped ) )
|
|
{
|
|
// Read completed
|
|
GetOverlappedResult( m_hSerialController, &overlapped, &bytesRead, TRUE );
|
|
|
|
// Copy to the stream buffer
|
|
if( bytesRead > 0 )
|
|
m_owner->Put( buffer, bytesRead );
|
|
}
|
|
else
|
|
{
|
|
//Wait for the read to complete
|
|
if( ERROR_IO_PENDING == GetLastError() )
|
|
{
|
|
// Wait for the read to complete or the
|
|
// signal that this thread should exit.
|
|
HANDLE handles[2];
|
|
handles[0] = overlapped.hEvent;
|
|
handles[1] = m_hExit;
|
|
DWORD res = WaitForMultipleObjects( 2, handles, FALSE, INFINITE );
|
|
|
|
if( (WAIT_OBJECT_0+1) == res )
|
|
{
|
|
// Exit signalled. Cancel the read.
|
|
goto exitRead;
|
|
}
|
|
|
|
if( WAIT_TIMEOUT == res )
|
|
{
|
|
// Timed out - should never happen
|
|
goto exitRead;
|
|
}
|
|
|
|
// Read completed
|
|
GetOverlappedResult( m_hSerialController, &overlapped, &bytesRead, TRUE );
|
|
|
|
// Copy to the stream buffer
|
|
if( bytesRead > 0 )
|
|
m_owner->Put( buffer, bytesRead );
|
|
}
|
|
else
|
|
{
|
|
// An error has occurred
|
|
goto exitRead;
|
|
}
|
|
}
|
|
}
|
|
while( bytesRead > 0 );
|
|
|
|
// Clear the event
|
|
ResetEvent( overlapped.hEvent );
|
|
|
|
// Nothing available to read, so wait for the next rx char event
|
|
DWORD dwEvtMask;
|
|
if( !WaitCommEvent( m_hSerialController, &dwEvtMask, &overlapped ) )
|
|
{
|
|
if( ERROR_IO_PENDING == GetLastError() )
|
|
{
|
|
// Wait for either some data to arrive or
|
|
// the signal that this thread should exit.
|
|
HANDLE handles[2];
|
|
handles[0] = overlapped.hEvent;
|
|
handles[1] = m_hExit;
|
|
|
|
DWORD res = WaitForMultipleObjects( 2, handles, FALSE, INFINITE );
|
|
|
|
if( (WAIT_OBJECT_0+1) == res )
|
|
{
|
|
// Exit signalled. Prevent WaitCommEvent from corrupting the
|
|
// stack by forcing it to exit.
|
|
goto exitRead;
|
|
}
|
|
|
|
if( WAIT_TIMEOUT == res )
|
|
{
|
|
// Timed out - should never happen
|
|
goto exitRead;
|
|
}
|
|
|
|
GetOverlappedResult( m_hSerialController, &overlapped, &bytesRead, TRUE );
|
|
}
|
|
}
|
|
|
|
// Clear the event
|
|
ResetEvent( overlapped.hEvent );
|
|
|
|
// Loop back to the top to read the waiting data
|
|
}
|
|
|
|
exitRead:
|
|
// Exit event has been signalled, or an error has occurred
|
|
SetCommMask( m_hSerialController, 0 );
|
|
CancelIo( m_hSerialController );
|
|
CloseHandle( overlapped.hEvent );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// <SerialControllerImpl::Write>
|
|
// Send data to the serial port
|
|
//-----------------------------------------------------------------------------
|
|
uint32 SerialControllerImpl::Write
|
|
(
|
|
uint8* _buffer,
|
|
uint32 _length
|
|
)
|
|
{
|
|
if( INVALID_HANDLE_VALUE == m_hSerialController )
|
|
{
|
|
//Error
|
|
Log::Write( LogLevel_Error, "ERROR: Serial port must be opened before writing\n" );
|
|
return 0;
|
|
}
|
|
|
|
// Write the data
|
|
OVERLAPPED overlapped;
|
|
memset( &overlapped, 0, sizeof(overlapped) );
|
|
overlapped.hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
|
|
DWORD bytesWritten;
|
|
if( !::WriteFile( m_hSerialController, _buffer, _length, &bytesWritten, &overlapped ) )
|
|
{
|
|
//Wait for the write to complete
|
|
if( ERROR_IO_PENDING == GetLastError() )
|
|
{
|
|
GetOverlappedResult( m_hSerialController, &overlapped, &bytesWritten, TRUE );
|
|
}
|
|
else
|
|
{
|
|
Log::Write( LogLevel_Error, "ERROR: Serial port write (0x%.8x)", GetLastError() );
|
|
}
|
|
}
|
|
|
|
CloseHandle( overlapped.hEvent );
|
|
return (uint32)bytesWritten;
|
|
}
|