diff --git a/celrs/platform/serial_internal.h b/celrs/platform/serial_internal.h index 406a311..7aa1c1a 100644 --- a/celrs/platform/serial_internal.h +++ b/celrs/platform/serial_internal.h @@ -1,5 +1,28 @@ #pragma once #include +#include + +/* Platform serial handle (HANDLE on Windows, fd on POSIX) */ +typedef intptr_t cel_serial_platform_handle; + +#define CEL_SERIAL_PLATFORM_INVALID_HANDLE ((cel_serial_platform_handle)-1) + +/* Open the platform serial device at path with the given baud rate. + * Returns CEL_SERIAL_PLATFORM_INVALID_HANDLE on failure. */ +cel_serial_platform_handle cel_serial_platform_open(char const* path, int baud_rate); + +/* Close the platform serial device. */ +void cel_serial_platform_close(cel_serial_platform_handle handle); + +/* Read up to len bytes without blocking. + * Returns bytes read immediately available, or 0 if none/error. */ +size_t cel_serial_platform_read(cel_serial_platform_handle handle, uint8_t* buf, size_t len); + +/* Write data. Returns bytes written, 0 on error. */ +size_t cel_serial_platform_write(cel_serial_platform_handle handle, uint8_t const* buf, size_t len); + +/* Flush output buffer. */ +void cel_serial_platform_flush(cel_serial_platform_handle handle); /* Platform-specific port enumeration. * Returns the number of ports found, or -1 on error. diff --git a/celrs/platform/serial_win.c b/celrs/platform/serial_win.c index 0e85b34..4e36a49 100644 --- a/celrs/platform/serial_win.c +++ b/celrs/platform/serial_win.c @@ -1,9 +1,67 @@ #include "celrs/platform/serial_internal.h" #include +#include #include #include +cel_serial_platform_handle cel_serial_platform_open(char const* path, int baud_rate) { + char full_path[300]; + snprintf(full_path, sizeof(full_path), "\\\\.\\%s", path); + + HANDLE h = CreateFileA(full_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) return CEL_SERIAL_PLATFORM_INVALID_HANDLE; + + DCB dcb = {0}; + dcb.DCBlength = sizeof(dcb); + if (!GetCommState(h, &dcb)) { + CloseHandle(h); + return CEL_SERIAL_PLATFORM_INVALID_HANDLE; + } + + dcb.BaudRate = (DWORD)baud_rate; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + + if (!SetCommState(h, &dcb)) { + CloseHandle(h); + return CEL_SERIAL_PLATFORM_INVALID_HANDLE; + } + + /* Non-blocking reads: ReadFile returns immediately with whatever + * bytes are already buffered (possibly zero). */ + COMMTIMEOUTS timeouts = {0}; + timeouts.ReadIntervalTimeout = MAXDWORD; + if (!SetCommTimeouts(h, &timeouts)) { + CloseHandle(h); + return CEL_SERIAL_PLATFORM_INVALID_HANDLE; + } + + return (cel_serial_platform_handle)(intptr_t)h; +} + +void cel_serial_platform_close(cel_serial_platform_handle handle) { + CloseHandle((HANDLE)(intptr_t)handle); +} + +size_t cel_serial_platform_read(cel_serial_platform_handle handle, uint8_t* buf, size_t len) { + DWORD bytes_read = 0; + if (!ReadFile((HANDLE)(intptr_t)handle, buf, (DWORD)len, &bytes_read, NULL)) return 0; + return (size_t)bytes_read; +} + +size_t cel_serial_platform_write(cel_serial_platform_handle handle, uint8_t const* buf, size_t len) { + DWORD bytes_written = 0; + if (!WriteFile((HANDLE)(intptr_t)handle, buf, (DWORD)len, &bytes_written, NULL)) return 0; + return (size_t)bytes_written; +} + +void cel_serial_platform_flush(cel_serial_platform_handle handle) { + FlushFileBuffers((HANDLE)(intptr_t)handle); +} + /* Enumerate active COM ports via HKLM\HARDWARE\DEVICEMAP\SERIALCOMM. * Windows keeps one value per active port in this key, so this is a * single registry read instead of probing COM1..COM255 with CreateFile. */ diff --git a/celrs/serial.c b/celrs/serial.c index da0b8e8..28c7b45 100644 --- a/celrs/serial.c +++ b/celrs/serial.c @@ -11,51 +11,48 @@ struct cel_serial_port { char path[256]; int baud_rate; - int fd; /* platform-specific handle (HANDLE on Win, int on POSIX) */ + cel_serial_platform_handle handle; }; cel_serial_port* cel_serial_open(char const* path, int baud_rate) { if (path == NULL) return NULL; + cel_serial_platform_handle handle = cel_serial_platform_open(path, baud_rate); + if (handle == CEL_SERIAL_PLATFORM_INVALID_HANDLE) return NULL; + cel_serial_port* port = (cel_serial_port*)calloc(1, sizeof(cel_serial_port)); - if (port == NULL) return NULL; + if (port == NULL) { + cel_serial_platform_close(handle); + return NULL; + } strncpy(port->path, path, sizeof(port->path) - 1); port->path[sizeof(port->path) - 1] = '\0'; port->baud_rate = baud_rate; - port->fd = -1; - - /* TODO: platform-specific open (CreateFile on Win, open+termios on POSIX) */ - (void)baud_rate; + port->handle = handle; return port; } void cel_serial_close(cel_serial_port* port) { if (port == NULL) return; - /* TODO: platform-specific close */ + cel_serial_platform_close(port->handle); free(port); } size_t cel_serial_read(cel_serial_port* port, uint8_t* buf, size_t len) { - (void)port; - (void)buf; - (void)len; - /* TODO: platform-specific non-blocking read */ - return 0; + if (port == NULL) return 0; + return cel_serial_platform_read(port->handle, buf, len); } size_t cel_serial_write(cel_serial_port* port, uint8_t const* buf, size_t len) { - (void)port; - (void)buf; - (void)len; - /* TODO: platform-specific write */ - return 0; + if (port == NULL) return 0; + return cel_serial_platform_write(port->handle, buf, len); } void cel_serial_flush(cel_serial_port* port) { - (void)port; - /* TODO: platform-specific flush */ + if (port == NULL) return; + cel_serial_platform_flush(port->handle); } int cel_serial_list_ports(char*** out_ports, int max_ports) { diff --git a/tests/test_serial.c b/tests/test_serial.c index 84434f4..81374b8 100644 --- a/tests/test_serial.c +++ b/tests/test_serial.c @@ -8,8 +8,11 @@ void setUp(void) { Mockserial_internal_Init(); } void tearDown(void) { Mockserial_internal_Verify(); Mockserial_internal_Destroy(); } void test_open_valid_path(void) { + cel_serial_platform_open_ExpectAndReturn("COM3", 400000, 1); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); + + cel_serial_platform_close_Expect(1); cel_serial_close(port); } @@ -17,10 +20,18 @@ void test_open_null_path(void) { TEST_ASSERT_NULL(cel_serial_open(NULL, 400000)); } +void test_open_platform_failure_returns_null(void) { + cel_serial_platform_open_ExpectAndReturn("COM3", 400000, CEL_SERIAL_PLATFORM_INVALID_HANDLE); + TEST_ASSERT_NULL(cel_serial_open("COM3", 400000)); +} + void test_open_preserves_path(void) { + cel_serial_platform_open_ExpectAndReturn("/dev/ttyUSB0", 400000, 1); cel_serial_port* port = cel_serial_open("/dev/ttyUSB0", 400000); TEST_ASSERT_NOT_NULL(port); /* path is stored internally; verify by roundtrip behavior */ + + cel_serial_platform_close_Expect(1); cel_serial_close(port); } @@ -29,30 +40,53 @@ void test_close_null(void) { cel_serial_close(NULL); } -void test_read_returns_zero_stub(void) { +void test_read_delegates_to_platform(void) { + cel_serial_platform_open_ExpectAndReturn("COM3", 400000, 1); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); + uint8_t buf[16]; - /* Stub implementation returns 0 */ + cel_serial_platform_read_ExpectAndReturn(1, buf, sizeof(buf), 4); size_t n = cel_serial_read(port, buf, sizeof(buf)); - TEST_ASSERT_EQUAL_UINT(0, n); + TEST_ASSERT_EQUAL_UINT(4, n); + + cel_serial_platform_close_Expect(1); cel_serial_close(port); } -void test_write_returns_zero_stub(void) { +void test_read_null_port_returns_zero(void) { + uint8_t buf[16]; + TEST_ASSERT_EQUAL_UINT(0, cel_serial_read(NULL, buf, sizeof(buf))); +} + +void test_write_delegates_to_platform(void) { + cel_serial_platform_open_ExpectAndReturn("COM3", 400000, 1); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); + uint8_t buf[4] = {0xC8, 0x10, 0x80, 0x03}; - /* Stub implementation returns 0 */ + cel_serial_platform_write_ExpectAndReturn(1, buf, sizeof(buf), sizeof(buf)); size_t n = cel_serial_write(port, buf, sizeof(buf)); - TEST_ASSERT_EQUAL_UINT(0, n); + TEST_ASSERT_EQUAL_UINT(sizeof(buf), n); + + cel_serial_platform_close_Expect(1); cel_serial_close(port); } -void test_flush_no_crash(void) { +void test_write_null_port_returns_zero(void) { + uint8_t buf[4] = {0xC8, 0x10, 0x80, 0x03}; + TEST_ASSERT_EQUAL_UINT(0, cel_serial_write(NULL, buf, sizeof(buf))); +} + +void test_flush_delegates_to_platform(void) { + cel_serial_platform_open_ExpectAndReturn("COM3", 400000, 1); cel_serial_port* port = cel_serial_open("COM3", 400000); TEST_ASSERT_NOT_NULL(port); - cel_serial_flush(port); /* should not crash */ + + cel_serial_platform_flush_Expect(1); + cel_serial_flush(port); + + cel_serial_platform_close_Expect(1); cel_serial_close(port); } @@ -91,11 +125,14 @@ int main(void) { UNITY_BEGIN(); RUN_TEST(test_open_valid_path); RUN_TEST(test_open_null_path); + RUN_TEST(test_open_platform_failure_returns_null); RUN_TEST(test_open_preserves_path); RUN_TEST(test_close_null); - RUN_TEST(test_read_returns_zero_stub); - RUN_TEST(test_write_returns_zero_stub); - RUN_TEST(test_flush_no_crash); + RUN_TEST(test_read_delegates_to_platform); + RUN_TEST(test_read_null_port_returns_zero); + RUN_TEST(test_write_delegates_to_platform); + RUN_TEST(test_write_null_port_returns_zero); + RUN_TEST(test_flush_delegates_to_platform); RUN_TEST(test_flush_null); RUN_TEST(test_list_ports_null_out); RUN_TEST(test_list_ports_passes_max_ports_through);