#ifdef ARCH_PORTDUINO #include "GpsdSerial.h" #include "configuration.h" #include #ifdef _WIN32 #include #include #include #else #include #include #include #include #include #include #endif namespace { // Winsock needs explicit init, closesocket(), ioctlsocket() and WSAGetLastError(). // SOCKET fits the header's `int` on Win64, and INVALID_SOCKET narrows to -1. #ifdef _WIN32 // Done lazily to keep the dependency local to the one file that needs it. void initSocketsOnce() { static std::once_flag flag; std::call_once(flag, [] { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); }); } void closeSocket(int fd) { ::closesocket(static_cast(fd)); } void setNonBlocking(int fd) { u_long mode = 1; ::ioctlsocket(static_cast(fd), FIONBIO, &mode); } // Winsock has no MSG_DONTWAIT, but the socket is already non-blocking so a plain // recv() has the same semantics. int recvNonBlocking(int fd, void *buf, size_t len) { return ::recv(static_cast(fd), static_cast(buf), static_cast(len), 0); } int sendAll(int fd, const void *buf, size_t len) { return ::send(static_cast(fd), static_cast(buf), static_cast(len), 0); } bool lastErrorWasWouldBlock() { return WSAGetLastError() == WSAEWOULDBLOCK; } #else void initSocketsOnce() {} void closeSocket(int fd) { ::close(fd); } void setNonBlocking(int fd) { ::fcntl(fd, F_SETFL, O_NONBLOCK); } int recvNonBlocking(int fd, void *buf, size_t len) { return static_cast(::recv(fd, buf, len, MSG_DONTWAIT)); } int sendAll(int fd, const void *buf, size_t len) { return static_cast(::write(fd, buf, len)); } bool lastErrorWasWouldBlock() { return errno == EAGAIN || errno == EWOULDBLOCK; } #endif } // namespace namespace arduino { // The single global instance used by PortduinoGlue and GPS::createGps(). GpsdSerial gpsdSerial; void GpsdSerial::setAddress(const std::string &host, int port) { _host = host; _port = port; } bool GpsdSerial::connectToGpsd() { if (_host.empty()) return false; initSocketsOnce(); struct addrinfo hints = {}, *res = nullptr; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; std::string portStr = std::to_string(_port); if (getaddrinfo(_host.c_str(), portStr.c_str(), &hints, &res) != 0 || !res) { LOG_WARN("gpsdSerial: can't resolve %s", _host.c_str()); return false; } // Try every address returned by getaddrinfo (e.g. ::1 before 127.0.0.1). int fd = -1; for (struct addrinfo *rp = res; rp != nullptr; rp = rp->ai_next) { fd = static_cast(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)); if (fd < 0) continue; if (connect(fd, rp->ai_addr, static_cast(rp->ai_addrlen)) == 0) break; // connected closeSocket(fd); fd = -1; } freeaddrinfo(res); if (fd < 0) { LOG_WARN("gpsdSerial: connect to %s:%d failed", _host.c_str(), _port); return false; } // Switch to non-blocking so available()/read() never stall the GPS thread. setNonBlocking(fd); // Ask gpsd to stream raw NMEA sentences. const char watchCmd[] = "?WATCH={\"enable\":true,\"nmea\":true}\n"; sendAll(fd, watchCmd, sizeof(watchCmd) - 1); _sockfd = fd; _rxBuf.clear(); LOG_INFO("gpsdSerial: connected to %s:%d", _host.c_str(), _port); return true; } void GpsdSerial::begin(unsigned long /*baud*/, uint16_t /*config*/) { if (_sockfd >= 0) end(); _lastConnectAttemptMs = 0; // force immediate connect on begin() connectToGpsd(); } void GpsdSerial::end() { if (_sockfd >= 0) { closeSocket(_sockfd); _sockfd = -1; } _rxBuf.clear(); } void GpsdSerial::fillBuffer() { if (_sockfd < 0) return; // Guard: if the buffer is already full, skip recv entirely so n is always // assigned before the post-loop disconnect check below. if (_rxBuf.size() >= RX_BUF_MAX) return; uint8_t tmp[256]; int n; while (_rxBuf.size() < RX_BUF_MAX && (n = recvNonBlocking(_sockfd, tmp, sizeof(tmp))) > 0) { size_t space = RX_BUF_MAX - _rxBuf.size(); size_t toCopy = (static_cast(n) < space) ? static_cast(n) : space; for (size_t i = 0; i < toCopy; i++) _rxBuf.push_back(tmp[i]); } if (n == 0 || (n < 0 && !lastErrorWasWouldBlock())) { // gpsd closed the connection or a real error occurred. LOG_WARN("gpsdSerial: disconnected, will retry"); closeSocket(_sockfd); _sockfd = -1; _rxBuf.clear(); } } int GpsdSerial::available() { if (_sockfd < 0) { // Throttle reconnect attempts to avoid log spam and repeated DNS lookups // when gpsd is unreachable. uint32_t now = millis(); if (now - _lastConnectAttemptMs >= RECONNECT_INTERVAL_MS) { _lastConnectAttemptMs = now; connectToGpsd(); } } fillBuffer(); return static_cast(_rxBuf.size()); } int GpsdSerial::peek() { if (_rxBuf.empty()) return -1; return static_cast(_rxBuf.front()); } int GpsdSerial::read() { if (_rxBuf.empty()) return -1; uint8_t c = _rxBuf.front(); _rxBuf.pop_front(); return static_cast(c); } } // namespace arduino #endif // ARCH_PORTDUINO