Merge upstream libdivecomputer changes from Jef Driesen. No major changes and no conflicts except for a trivial one where Jef had marked a couple of private functions 'static' and our versions had extended the argument lists of those functions. This just updates to the current upstream state of libdivecomputer, where the bulk of the changes come mainly from the new ringbuffer helper code, but there are various other small misc fixes and cleanups. * git://git.libdivecomputer.org/libdivecomputer: Disable the deco events Replace the deco events with a deco sample Report errors from the close function Mark the private function as static Fix a bug in the tank pressure samples Disable freedive mode for the Uwatec Aladin Tec 2G Mark the private function as static Fix some compiler warnings Fix some more null pointer dereferences Use a more efficient download algorithm Use the new ringbuffer stream Add a common ringbuffer reading algorithm Improve the robustness of the IrDA I/O code Fix a few null pointer dereferences Fix the number of gas mixes Always use the sample timestamp as the base value
725 lines
17 KiB
C
725 lines
17 KiB
C
/*
|
|
* libdivecomputer
|
|
*
|
|
* Copyright (C) 2008 Jef Driesen
|
|
*
|
|
* This library 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 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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 this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
|
|
#define NOGDI
|
|
#include <windows.h>
|
|
|
|
#include "serial.h"
|
|
#include "common-private.h"
|
|
#include "context-private.h"
|
|
|
|
struct dc_serial_t {
|
|
/* Library context. */
|
|
dc_context_t *context;
|
|
/*
|
|
* The file descriptor corresponding to the serial port.
|
|
*/
|
|
HANDLE hFile;
|
|
/*
|
|
* Serial port settings are saved into this variables immediately
|
|
* after the port is opened. These settings are restored when the
|
|
* serial port is closed.
|
|
*/
|
|
DCB dcb;
|
|
COMMTIMEOUTS timeouts;
|
|
/* Half-duplex settings */
|
|
int halfduplex;
|
|
unsigned int baudrate;
|
|
unsigned int nbits;
|
|
};
|
|
|
|
static dc_status_t
|
|
syserror(DWORD errcode)
|
|
{
|
|
switch (errcode) {
|
|
case ERROR_INVALID_PARAMETER:
|
|
return DC_STATUS_INVALIDARGS;
|
|
case ERROR_OUTOFMEMORY:
|
|
return DC_STATUS_NOMEMORY;
|
|
case ERROR_FILE_NOT_FOUND:
|
|
return DC_STATUS_NODEVICE;
|
|
case ERROR_ACCESS_DENIED:
|
|
return DC_STATUS_NOACCESS;
|
|
default:
|
|
return DC_STATUS_IO;
|
|
}
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_enumerate (dc_serial_callback_t callback, void *userdata)
|
|
{
|
|
// Open the registry key.
|
|
HKEY hKey;
|
|
LONG rc = RegOpenKeyExA (HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_QUERY_VALUE, &hKey);
|
|
if (rc != ERROR_SUCCESS) {
|
|
if (rc == ERROR_FILE_NOT_FOUND)
|
|
return DC_STATUS_SUCCESS;
|
|
else
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Get the number of values.
|
|
DWORD count = 0;
|
|
rc = RegQueryInfoKey (hKey, NULL, NULL, NULL, NULL, NULL, NULL, &count, NULL, NULL, NULL, NULL);
|
|
if (rc != ERROR_SUCCESS) {
|
|
RegCloseKey(hKey);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
for (DWORD i = 0; i < count; ++i) {
|
|
// Get the value name, data and type.
|
|
char name[512], data[512];
|
|
DWORD name_len = sizeof (name);
|
|
DWORD data_len = sizeof (data);
|
|
DWORD type = 0;
|
|
rc = RegEnumValueA (hKey, i, name, &name_len, NULL, &type, (LPBYTE) data, &data_len);
|
|
if (rc != ERROR_SUCCESS) {
|
|
RegCloseKey(hKey);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Ignore non-string values.
|
|
if (type != REG_SZ)
|
|
continue;
|
|
|
|
// Prevent a possible buffer overflow.
|
|
if (data_len >= sizeof (data)) {
|
|
RegCloseKey(hKey);
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
// Null terminate the string.
|
|
data[data_len] = 0;
|
|
|
|
callback (data, userdata);
|
|
}
|
|
|
|
RegCloseKey(hKey);
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_open (dc_serial_t **out, dc_context_t *context, const char *name)
|
|
{
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
|
|
if (out == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (context, "Open: name=%s", name ? name : "");
|
|
|
|
// Build the device name.
|
|
const char *devname = NULL;
|
|
char buffer[MAX_PATH] = "\\\\.\\";
|
|
if (name && strncmp (name, buffer, 4) != 0) {
|
|
size_t length = strlen (name) + 1;
|
|
if (length + 4 > sizeof (buffer))
|
|
return DC_STATUS_NOMEMORY;
|
|
memcpy (buffer + 4, name, length);
|
|
devname = buffer;
|
|
} else {
|
|
devname = name;
|
|
}
|
|
|
|
// Allocate memory.
|
|
dc_serial_t *device = (dc_serial_t *) malloc (sizeof (dc_serial_t));
|
|
if (device == NULL) {
|
|
SYSERROR (context, ERROR_OUTOFMEMORY);
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
// Library context.
|
|
device->context = context;
|
|
|
|
// Default to full-duplex.
|
|
device->halfduplex = 0;
|
|
device->baudrate = 0;
|
|
device->nbits = 0;
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(context, *out = device, open, name);
|
|
|
|
// Open the device.
|
|
device->hFile = CreateFileA (devname,
|
|
GENERIC_READ | GENERIC_WRITE, 0,
|
|
NULL, // No security attributes.
|
|
OPEN_EXISTING,
|
|
0, // Non-overlapped I/O.
|
|
NULL);
|
|
if (device->hFile == INVALID_HANDLE_VALUE) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (context, errcode);
|
|
status = syserror (errcode);
|
|
goto error_free;
|
|
}
|
|
|
|
// Retrieve the current communication settings and timeouts,
|
|
// to be able to restore them when closing the device.
|
|
// It is also used to check if the obtained handle
|
|
// represents a serial device.
|
|
if (!GetCommState (device->hFile, &device->dcb) ||
|
|
!GetCommTimeouts (device->hFile, &device->timeouts)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (context, errcode);
|
|
status = syserror (errcode);
|
|
goto error_close;
|
|
}
|
|
|
|
*out = device;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
|
|
error_close:
|
|
CloseHandle (device->hFile);
|
|
error_free:
|
|
free (device);
|
|
return status;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_close (dc_serial_t *device)
|
|
{
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
|
|
if (device == NULL)
|
|
return DC_STATUS_SUCCESS;
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, free(device), close);
|
|
|
|
// Restore the initial communication settings and timeouts.
|
|
if (!SetCommState (device->hFile, &device->dcb) ||
|
|
!SetCommTimeouts (device->hFile, &device->timeouts)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
dc_status_set_error(&status, syserror (errcode));
|
|
}
|
|
|
|
// Close the device.
|
|
if (!CloseHandle (device->hFile)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
dc_status_set_error(&status, syserror (errcode));
|
|
}
|
|
|
|
// Free memory.
|
|
free (device);
|
|
|
|
return status;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_configure (dc_serial_t *device, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Configure: baudrate=%i, databits=%i, parity=%i, stopbits=%i, flowcontrol=%i",
|
|
baudrate, databits, parity, stopbits, flowcontrol);
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , configure, baudrate, databits, parity, stopbits, flowcontrol);
|
|
|
|
// Retrieve the current settings.
|
|
DCB dcb;
|
|
if (!GetCommState (device->hFile, &dcb)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
dcb.fBinary = TRUE; // Enable Binary Transmission
|
|
dcb.fAbortOnError = FALSE;
|
|
|
|
// Baudrate.
|
|
dcb.BaudRate = baudrate;
|
|
|
|
// Character size.
|
|
if (databits >= 5 && databits <= 8)
|
|
dcb.ByteSize = databits;
|
|
else
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
// Parity checking.
|
|
switch (parity) {
|
|
case DC_PARITY_NONE:
|
|
dcb.Parity = NOPARITY;
|
|
dcb.fParity = FALSE;
|
|
break;
|
|
case DC_PARITY_EVEN:
|
|
dcb.Parity = EVENPARITY;
|
|
dcb.fParity = TRUE;
|
|
break;
|
|
case DC_PARITY_ODD:
|
|
dcb.Parity = ODDPARITY;
|
|
dcb.fParity = TRUE;
|
|
break;
|
|
case DC_PARITY_MARK:
|
|
dcb.Parity = MARKPARITY;
|
|
dcb.fParity = TRUE;
|
|
break;
|
|
case DC_PARITY_SPACE:
|
|
dcb.Parity = SPACEPARITY;
|
|
dcb.fParity = TRUE;
|
|
break;
|
|
default:
|
|
return DC_STATUS_INVALIDARGS;
|
|
}
|
|
|
|
// Stopbits.
|
|
switch (stopbits) {
|
|
case DC_STOPBITS_ONE:
|
|
dcb.StopBits = ONESTOPBIT;
|
|
break;
|
|
case DC_STOPBITS_ONEPOINTFIVE:
|
|
dcb.StopBits = ONE5STOPBITS;
|
|
break;
|
|
case DC_STOPBITS_TWO:
|
|
dcb.StopBits = TWOSTOPBITS;
|
|
break;
|
|
default:
|
|
return DC_STATUS_INVALIDARGS;
|
|
}
|
|
|
|
// Flow control.
|
|
switch (flowcontrol) {
|
|
case DC_FLOWCONTROL_NONE:
|
|
dcb.fInX = FALSE;
|
|
dcb.fOutX = FALSE;
|
|
dcb.fOutxCtsFlow = FALSE;
|
|
dcb.fOutxDsrFlow = FALSE;
|
|
dcb.fDtrControl = DTR_CONTROL_ENABLE;
|
|
dcb.fRtsControl = RTS_CONTROL_ENABLE;
|
|
break;
|
|
case DC_FLOWCONTROL_HARDWARE:
|
|
dcb.fInX = FALSE;
|
|
dcb.fOutX = FALSE;
|
|
dcb.fOutxCtsFlow = TRUE;
|
|
dcb.fOutxDsrFlow = TRUE;
|
|
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
|
|
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
|
|
break;
|
|
case DC_FLOWCONTROL_SOFTWARE:
|
|
dcb.fInX = TRUE;
|
|
dcb.fOutX = TRUE;
|
|
dcb.fOutxCtsFlow = FALSE;
|
|
dcb.fOutxDsrFlow = FALSE;
|
|
dcb.fDtrControl = DTR_CONTROL_ENABLE;
|
|
dcb.fRtsControl = RTS_CONTROL_ENABLE;
|
|
break;
|
|
default:
|
|
return DC_STATUS_INVALIDARGS;
|
|
}
|
|
|
|
// Apply the new settings.
|
|
if (!SetCommState (device->hFile, &dcb)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
device->baudrate = baudrate;
|
|
device->nbits = 1 + databits + stopbits + (parity ? 1 : 0);
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_set_timeout (dc_serial_t *device, int timeout)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Timeout: value=%i", timeout);
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , set_timeout, timeout);
|
|
|
|
// Retrieve the current timeouts.
|
|
COMMTIMEOUTS timeouts;
|
|
if (!GetCommTimeouts (device->hFile, &timeouts)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
// Update the settings.
|
|
if (timeout < 0) {
|
|
// Blocking mode.
|
|
timeouts.ReadIntervalTimeout = 0;
|
|
timeouts.ReadTotalTimeoutMultiplier = 0;
|
|
timeouts.ReadTotalTimeoutConstant = 0;
|
|
timeouts.WriteTotalTimeoutMultiplier = 0;
|
|
timeouts.WriteTotalTimeoutConstant = 0;
|
|
} else if (timeout == 0) {
|
|
// Non-blocking mode.
|
|
timeouts.ReadIntervalTimeout = MAXDWORD;
|
|
timeouts.ReadTotalTimeoutMultiplier = 0;
|
|
timeouts.ReadTotalTimeoutConstant = 0;
|
|
timeouts.WriteTotalTimeoutMultiplier = 0;
|
|
timeouts.WriteTotalTimeoutConstant = 0;
|
|
} else {
|
|
// Standard timeout mode.
|
|
timeouts.ReadIntervalTimeout = 0;
|
|
timeouts.ReadTotalTimeoutMultiplier = 0;
|
|
timeouts.ReadTotalTimeoutConstant = timeout;
|
|
timeouts.WriteTotalTimeoutMultiplier = 0;
|
|
timeouts.WriteTotalTimeoutConstant = 0;
|
|
}
|
|
|
|
// Activate the new timeouts.
|
|
if (!SetCommTimeouts (device->hFile, &timeouts)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_set_halfduplex (dc_serial_t *device, unsigned int value)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , set_halfduplex, value);
|
|
|
|
device->halfduplex = value;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_set_latency (dc_serial_t *device, unsigned int value)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_read (dc_serial_t *device, void *data, size_t size, size_t *actual)
|
|
{
|
|
size_t nbytes = 0;
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
DWORD dwRead = 0;
|
|
|
|
if (device == NULL) {
|
|
status = DC_STATUS_INVALIDARGS;
|
|
goto out_invalidargs;
|
|
}
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context,
|
|
{
|
|
HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Custom Read", (unsigned char *) data, nbytes);
|
|
if (actual)
|
|
*actual = nbytes;
|
|
},
|
|
read, data, size, &nbytes);
|
|
|
|
if (!ReadFile (device->hFile, data, size, &dwRead, NULL)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror (errcode);
|
|
goto out;
|
|
}
|
|
|
|
if (dwRead != size) {
|
|
status = DC_STATUS_TIMEOUT;
|
|
}
|
|
|
|
out:
|
|
HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, dwRead);
|
|
|
|
out_invalidargs:
|
|
if (actual)
|
|
*actual = dwRead;
|
|
|
|
return status;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_write (dc_serial_t *device, const void *data, size_t size, size_t *actual)
|
|
{
|
|
size_t nbytes = 0;
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
DWORD dwWritten = 0;
|
|
|
|
if (device == NULL) {
|
|
status = DC_STATUS_INVALIDARGS;
|
|
goto out_invalidargs;
|
|
}
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context,
|
|
{
|
|
HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Custom Write", (unsigned char *) data, nbytes);
|
|
if (actual)
|
|
*actual = nbytes;
|
|
},
|
|
write, data, size, &nbytes);
|
|
|
|
LARGE_INTEGER begin, end, freq;
|
|
if (device->halfduplex) {
|
|
// Get the current time.
|
|
if (!QueryPerformanceFrequency(&freq) ||
|
|
!QueryPerformanceCounter(&begin)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror (errcode);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!WriteFile (device->hFile, data, size, &dwWritten, NULL)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror (errcode);
|
|
goto out;
|
|
}
|
|
|
|
if (device->halfduplex) {
|
|
// Get the current time.
|
|
if (!QueryPerformanceCounter(&end)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
status = syserror (errcode);
|
|
goto out;
|
|
}
|
|
|
|
// Calculate the elapsed time (microseconds).
|
|
unsigned long elapsed = 1000000.0 * (end.QuadPart - begin.QuadPart) / freq.QuadPart + 0.5;
|
|
|
|
// Calculate the expected duration (microseconds). A 2 millisecond fudge
|
|
// factor is added because it improves the success rate significantly.
|
|
unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000;
|
|
|
|
// Wait for the remaining time.
|
|
if (elapsed < expected) {
|
|
unsigned long remaining = expected - elapsed;
|
|
|
|
// The remaining time is rounded up to the nearest millisecond
|
|
// because the Windows Sleep() function doesn't have a higher
|
|
// resolution.
|
|
dc_serial_sleep (device, (remaining + 999) / 1000);
|
|
}
|
|
}
|
|
|
|
if (dwWritten != size) {
|
|
status = DC_STATUS_TIMEOUT;
|
|
}
|
|
|
|
out:
|
|
HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Write", (unsigned char *) data, dwWritten);
|
|
|
|
out_invalidargs:
|
|
if (actual)
|
|
*actual = dwWritten;
|
|
|
|
return status;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_purge (dc_serial_t *device, dc_direction_t direction)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Purge: direction=%u", direction);
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , purge, direction);
|
|
|
|
DWORD flags = 0;
|
|
|
|
switch (direction) {
|
|
case DC_DIRECTION_INPUT:
|
|
flags = PURGE_RXABORT | PURGE_RXCLEAR;
|
|
break;
|
|
case DC_DIRECTION_OUTPUT:
|
|
flags = PURGE_TXABORT | PURGE_TXCLEAR;
|
|
break;
|
|
case DC_DIRECTION_ALL:
|
|
flags = PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR;
|
|
break;
|
|
default:
|
|
return DC_STATUS_INVALIDARGS;
|
|
}
|
|
|
|
if (!PurgeComm (device->hFile, flags)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_flush (dc_serial_t *device)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Flush: none");
|
|
|
|
if (!FlushFileBuffers (device->hFile)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_set_break (dc_serial_t *device, unsigned int level)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Break: value=%i", level);
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , set_break, level);
|
|
|
|
if (level) {
|
|
if (!SetCommBreak (device->hFile)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
} else {
|
|
if (!ClearCommBreak (device->hFile)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_set_dtr (dc_serial_t *device, unsigned int level)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "DTR: value=%i", level);
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , set_dtr, level);
|
|
|
|
int status = (level ? SETDTR : CLRDTR);
|
|
|
|
if (!EscapeCommFunction (device->hFile, status)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_set_rts (dc_serial_t *device, unsigned int level)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "RTS: value=%i", level);
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , set_rts, level);
|
|
|
|
int status = (level ? SETRTS : CLRRTS);
|
|
|
|
if (!EscapeCommFunction (device->hFile, status)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_get_available (dc_serial_t *device, size_t *value)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
RETURN_IF_CUSTOM_SERIAL(device->context, , get_available, value);
|
|
|
|
COMSTAT stats;
|
|
|
|
if (!ClearCommError (device->hFile, NULL, &stats)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
if (value)
|
|
*value = stats.cbInQue;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_get_lines (dc_serial_t *device, unsigned int *value)
|
|
{
|
|
unsigned int lines = 0;
|
|
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
DWORD stats = 0;
|
|
if (!GetCommModemStatus (device->hFile, &stats)) {
|
|
DWORD errcode = GetLastError ();
|
|
SYSERROR (device->context, errcode);
|
|
return syserror (errcode);
|
|
}
|
|
|
|
if (stats & MS_RLSD_ON)
|
|
lines |= DC_LINE_DCD;
|
|
if (stats & MS_CTS_ON)
|
|
lines |= DC_LINE_CTS;
|
|
if (stats & MS_DSR_ON)
|
|
lines |= DC_LINE_DSR;
|
|
if (stats & MS_RING_ON)
|
|
lines |= DC_LINE_RNG;
|
|
|
|
if (value)
|
|
*value = lines;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
dc_serial_sleep (dc_serial_t *device, unsigned int timeout)
|
|
{
|
|
if (device == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
INFO (device->context, "Sleep: value=%u", timeout);
|
|
|
|
Sleep (timeout);
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|