diff --git a/src/dmx.cpp b/src/dmx.cpp index 623b792..5c3d564 100644 --- a/src/dmx.cpp +++ b/src/dmx.cpp @@ -2,62 +2,26 @@ // Created by illyum on 8/15/2024. // -#include "dmx.h" -#include -#include -#include -#include "windows.h" -DMXEngine::DMXEngine(const char* portName, int baudRate, int dataSize) - : running(false), buffer(dataSize, 0) { - hSerial = CreateFileA(portName, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); - if (hSerial == INVALID_HANDLE_VALUE) { - std::cerr << "Error opening " << portName << " port" << std::endl; +#include "dmx.h" +#include "serial_port_factory.h" +#include +#include + +DMXEngine::DMXEngine(const char* portName) + : running(false), buffer(DMX_CHANNELS, 0) { + serialPort = SerialPortFactory::createSerialPort(); + if (!serialPort->open(portName, DMX_BAUD_RATE)) { + std::cerr << "Failed to open serial port" << std::endl; exit(EXIT_FAILURE); } - - configurePort(); } DMXEngine::~DMXEngine() { if (running) { stop(); } - CloseHandle(hSerial); -} - -void DMXEngine::configurePort() { - DCB dcbSerialParams = { 0 }; - dcbSerialParams.DCBlength = sizeof(dcbSerialParams); - if (!GetCommState(hSerial, &dcbSerialParams)) { - std::cerr << "Error getting COM port state" << std::endl; - CloseHandle(hSerial); - exit(EXIT_FAILURE); - } - - dcbSerialParams.BaudRate = 250000; - dcbSerialParams.ByteSize = 8; - dcbSerialParams.StopBits = TWOSTOPBITS; - dcbSerialParams.Parity = NOPARITY; - - if (!SetCommState(hSerial, &dcbSerialParams)) { - std::cerr << "Error setting COM port state" << std::endl; - CloseHandle(hSerial); - exit(EXIT_FAILURE); - } - - COMMTIMEOUTS timeouts = { 0 }; - timeouts.ReadIntervalTimeout = 50; - timeouts.ReadTotalTimeoutConstant = 50; - timeouts.ReadTotalTimeoutMultiplier = 10; - timeouts.WriteTotalTimeoutConstant = 50; - timeouts.WriteTotalTimeoutMultiplier = 10; - - if (!SetCommTimeouts(hSerial, &timeouts)) { - std::cerr << "Error setting COM port timeouts" << std::endl; - CloseHandle(hSerial); - exit(EXIT_FAILURE); - } + delete serialPort; } void DMXEngine::start() { @@ -74,26 +38,21 @@ void DMXEngine::stop() { DMXEngine::dumpZeros(); } - void DMXEngine::setChannelValue(int channel, unsigned char value) { - if (channel >= 0 && channel < buffer.size()) { + if (channel >= 0 && channel < DMX_CHANNELS) { buffer[channel] = value; } } void DMXEngine::sendDMXData() { while (running) { - // Break condition - EscapeCommFunction(hSerial, SETBREAK); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - EscapeCommFunction(hSerial, CLRBREAK); + serialPort->sendBreak(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); // MAB - DWORD bytesWritten; - unsigned char startCode = 0; - WriteFile(hSerial, &startCode, 1, &bytesWritten, NULL); - WriteFile(hSerial, buffer.data(), buffer.size(), &bytesWritten, NULL); + std::vector data = {0}; // Start code + data.insert(data.end(), buffer.begin(), buffer.end()); + serialPort->writeData(data); std::this_thread::sleep_for(std::chrono::milliseconds(25)); } } @@ -101,17 +60,16 @@ void DMXEngine::sendDMXData() { void DMXEngine::dumpZeros() { std::fill(buffer.begin(), buffer.end(), 0); - EscapeCommFunction(hSerial, SETBREAK); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - EscapeCommFunction(hSerial, CLRBREAK); + serialPort->sendBreak(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); // MAB - DWORD bytesWritten; - unsigned char startCode = 0; - WriteFile(hSerial, &startCode, 1, &bytesWritten, NULL); - WriteFile(hSerial, buffer.data(), buffer.size(), &bytesWritten, NULL); + std::vector data = {0}; // Start code + data.insert(data.end(), buffer.begin(), buffer.end()); + + serialPort->writeData(data); } void DMXEngine::dmxThreadFunc(DMXEngine* engine) { engine->sendDMXData(); } + diff --git a/src/dmx.h b/src/dmx.h index eceb48f..f9d99f8 100644 --- a/src/dmx.h +++ b/src/dmx.h @@ -9,6 +9,9 @@ #include #include #include +#include "serial_port.h" + +const int DMX_BAUD_RATE = 250000; struct DMXFixture { unsigned char r, g, b; @@ -17,7 +20,7 @@ struct DMXFixture { class DMXEngine { public: - explicit DMXEngine(const char* portName, int baudRate = 250000, int dataSize = 512); + DMXEngine(const char* portName); ~DMXEngine(); void start(); @@ -25,15 +28,17 @@ public: void setChannelValue(int channel, unsigned char value); private: + void configurePort(); void sendDMXData(); void dumpZeros(); - void configurePort(); static void dmxThreadFunc(DMXEngine* engine); - void* hSerial; - std::atomic running; + SerialPort* serialPort; std::vector buffer; std::thread dmxThread; + bool running; + + static const int DMX_CHANNELS = 512; }; #endif // DMX_H diff --git a/src/serial_port.h b/src/serial_port.h new file mode 100644 index 0000000..1cabbb1 --- /dev/null +++ b/src/serial_port.h @@ -0,0 +1,22 @@ +// +// Created by illyum on 8/16/2024. +// + +#ifndef SERIAL_PORT_H +#define SERIAL_PORT_H + +#include +#include + +class SerialPort { +public: + virtual ~SerialPort() = default; + + virtual bool open(const std::string& portName, int baudRate) = 0; + virtual void close() = 0; + virtual bool configure(int baudRate, int dataBits, int stopBits, int parity) = 0; + virtual bool writeData(const std::vector& data) = 0; + virtual bool sendBreak() = 0; +}; + +#endif // SERIAL_PORT_H \ No newline at end of file diff --git a/src/serial_port_factory.h b/src/serial_port_factory.h new file mode 100644 index 0000000..4c1aed0 --- /dev/null +++ b/src/serial_port_factory.h @@ -0,0 +1,25 @@ +// +// Created by illyum on 8/16/2024. +// + +#ifndef SERIAL_PORT_FACTORY_H +#define SERIAL_PORT_FACTORY_H + +#include "serial_port.h" + +#ifdef _WIN32 +#include "serial_port_windows.cpp" +typedef WindowsSerialPort PlatformSerialPort; +#elif defined(__linux__) || defined(__APPLE__) +#include "serial_port_posix.cpp" +typedef PosixSerialPort PlatformSerialPort; +#endif + +class SerialPortFactory { +public: + static SerialPort* createSerialPort() { + return new PlatformSerialPort(); + } +}; + +#endif // SERIAL_PORT_FACTORY_H diff --git a/src/serial_port_posix.cpp b/src/serial_port_posix.cpp new file mode 100644 index 0000000..30e33fd --- /dev/null +++ b/src/serial_port_posix.cpp @@ -0,0 +1,83 @@ +// +// Created by illyum on 8/16/2024. +// + +// FIXME: +// I don't have a mac to test it on so... I have zero idea if +// this will work, let alone compile + +#if defined(__linux__) || defined(__APPLE__) +#include "serial_port.h" +#include +#include +#include +#include + +class PosixSerialPort : public SerialPort { +public: + PosixSerialPort() : fd(-1) {} + + ~PosixSerialPort() { + close(); + } + + bool open(const std::string& portName, int baudRate) override { + fd = ::open(portName.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); + if (fd == -1) { + std::cerr << "Error opening " << portName << " port" << std::endl; + return false; + } + return configure(baudRate, CS8, 1, 0); + } + + void close() override { + if (fd != -1) { + ::close(fd); + fd = -1; + } + } + + bool configure(int baudRate, int dataBits, int stopBits, int parity) override { + struct termios options; + if (tcgetattr(fd, &options) < 0) { + std::cerr << "Error getting COM port attributes" << std::endl; + return false; + } + + cfsetispeed(&options, baudRate); + cfsetospeed(&options, baudRate); + + options.c_cflag &= ~CSIZE; + options.c_cflag |= dataBits; + + options.c_cflag &= ~PARENB; + if (parity) options.c_cflag |= PARENB; + + options.c_cflag &= ~CSTOPB; + if (stopBits == 2) options.c_cflag |= CSTOPB; + + options.c_cflag |= CLOCAL | CREAD; + + options.c_iflag &= ~(IXON | IXOFF | IXANY); + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_oflag &= ~OPOST; + + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 5; + + tcflush(fd, TCIFLUSH); + return tcsetattr(fd, TCSANOW, &options) == 0; + } + + bool writeData(const std::vector& data) override { + return ::write(fd, data.data(), data.size()) != -1; + } + + bool sendBreak() override { + return tcsendbreak(fd, 0) == 0; + } + +private: + int fd; +}; +#endif diff --git a/src/serial_port_windows.cpp b/src/serial_port_windows.cpp new file mode 100644 index 0000000..d0cc452 --- /dev/null +++ b/src/serial_port_windows.cpp @@ -0,0 +1,78 @@ +// +// Created by illyum on 8/16/2024. + +#ifdef _WIN32 +#include "serial_port.h" +#include +#include +#include + +class WindowsSerialPort : public SerialPort { +public: + WindowsSerialPort() : hSerial(INVALID_HANDLE_VALUE) {} + + ~WindowsSerialPort() { + close(); + } + + bool open(const std::string& portName, int baudRate) override { + hSerial = CreateFileA(portName.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hSerial == INVALID_HANDLE_VALUE) { + std::cerr << "Error opening " << portName << " port" << std::endl; + return false; + } + return configure(baudRate, 8, TWOSTOPBITS, NOPARITY); + } + + void close() override { + if (hSerial != INVALID_HANDLE_VALUE) { + CloseHandle(hSerial); + hSerial = INVALID_HANDLE_VALUE; + } + } + + bool configure(int baudRate, int dataBits, int stopBits, int parity) override { + DCB dcbSerialParams = { 0 }; + dcbSerialParams.DCBlength = sizeof(dcbSerialParams); + if (!GetCommState(hSerial, &dcbSerialParams)) { + std::cerr << "Error getting COM port state" << std::endl; + return false; + } + + dcbSerialParams.BaudRate = baudRate; + dcbSerialParams.ByteSize = dataBits; + dcbSerialParams.StopBits = stopBits; + dcbSerialParams.Parity = parity; + + if (!SetCommState(hSerial, &dcbSerialParams)) { + std::cerr << "Error setting COM port state" << std::endl; + return false; + } + + COMMTIMEOUTS timeouts = { 0 }; + timeouts.ReadIntervalTimeout = 50; + timeouts.ReadTotalTimeoutConstant = 50; + timeouts.ReadTotalTimeoutMultiplier = 10; + timeouts.WriteTotalTimeoutConstant = 50; + timeouts.WriteTotalTimeoutMultiplier = 10; + + return SetCommTimeouts(hSerial, &timeouts); + } + + bool writeData(const std::vector& data) override { + DWORD bytesWritten; + return WriteFile(hSerial, data.data(), data.size(), &bytesWritten, nullptr); + } + + bool sendBreak() override { + EscapeCommFunction(hSerial, SETBREAK); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + EscapeCommFunction(hSerial, CLRBREAK); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // MAB + return true; + } + +private: + HANDLE hSerial; +}; +#endif