/** * @file NTRIPClient.cpp * @author Alexander Klein (alex@kleiax.de) * @brief Contains the implementation of the class NTRIPClient. * @version 0.1 * @date 2022-09-18 * * @copyright Copyright (c) 2022 * */ #include "ntripClient.h" NTRIPClient::NTRIPClient(SFE_UBLOX_GNSS *gps, const char *host, uint16_t port, const char *mountPoint, const char *user, const char *password) : gps{gps}, port{port}, host{host}, mountPoint{mountPoint}, user{user}, password{password}, ntripClient{new WiFiClient}, state{NTRIPClientStates::closeConnection} { Component::loopDelay = NTRIPClient::loopDelay; } NTRIPClient::~NTRIPClient() { delete this->ntripClient; } void NTRIPClient::run() { switch (this->state) { case NTRIPClientStates::openConnection: if (!this->activated) { this->state = NTRIPClientStates::closeConnection; break; } std::cout << "Connecting to the NTRIP caster..." << std::endl; if (this->beginClient()) { std::cout << "Connected to the NTRIP caster!" << std::endl; this->state = NTRIPClientStates::pushData; } else { std::cout << "Failed!" << std::endl; this->state = NTRIPClientStates::wait; this->activated = false; } break; case NTRIPClientStates::pushData: if (!processConnection() || !this->activated) { this->state = NTRIPClientStates::closeConnection; } break; case NTRIPClientStates::closeConnection: std::cout << "Closing the connection to the NTRIP caster..." << std::endl; this->closeConnection(); state = NTRIPClientStates::wait; break; case NTRIPClientStates::wait: if (this->activated) { this->state = NTRIPClientStates::openConnection; } else { this->checkAutoReconnect(); } break; case NTRIPClientStates::notAvailable: break; default: std::cout << "Wrong state in NTRIPClient.cpp..." << std::endl; this->state = NTRIPClientStates::closeConnection; break; } } void NTRIPClient::runAsChild() { this->pushGPGGA(); } void NTRIPClient::gpsConfiguration() { this->gps->setSPIOutput(COM_TYPE_UBX | COM_TYPE_NMEA); this->gps->setPortInput(COM_PORT_SPI, COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3); // Set the differential mode - ambiguities are fixed whenever possible this->gps->setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); this->gps->setMainTalkerID(SFE_UBLOX_MAIN_TALKER_ID_GP); this->gps->enableNMEAMessage(UBX_NMEA_GGA, COM_PORT_SPI, 10); } bool NTRIPClient::setActivated(bool state) { if (state && this->state != NTRIPClientStates::notAvailable) { this->activated = true; } else if (state) { return false; } else { this->activated = false; this->autoReconnect = false; } return true; } void NTRIPClient::setAutoReconnect(bool state) { if (state) { this->reconnectAttemps = 0; this->autoReconnect = true; return; } this->autoReconnect = false; } bool NTRIPClient::beginClient() { static constexpr uint16_t httpError = 401; static constexpr uint16_t httpCheck = 200; std::cout << "Opening socket to " << this->host << std::endl; char serverRequest[this->bufferSize]; char credentials[this->bufferSize]; if (!static_cast(this->ntripClient->connect(static_cast(this->host), this->port))) { std::cout << "Connection to caster failed" << std::endl; return false; } std::cout << "Connected to " << this->host << " : " << this->port << std::endl; std::cout << "Requesting NTRIP Data from mount point " << this->mountPoint << std::endl; // Generate the server request (GET) snprintf(static_cast(serverRequest), this->bufferSize, static_cast("GET /%s HTTP/1.0\r\nUser-Agent: NTRIP SparkFun u-blox Client v1.0\r\n"), this->mountPoint); // Credentials const uint8_t userCredentialsLength = strlen(this->user) + strlen(this->password) + 2; auto *userCredentials = new char[userCredentialsLength]; snprintf(static_cast(userCredentials), userCredentialsLength, static_cast("%s:%s"), this->user, this->password); std::cout << "Sending credentials: " << userCredentials << std::endl; // Encode const base64 base; const String strEncodedCredentials = base64::encode(userCredentials); delete userCredentials; char encodedCredentials[strEncodedCredentials.length() + 1]; strEncodedCredentials.toCharArray(static_cast(encodedCredentials), sizeof(encodedCredentials)); snprintf(credentials, sizeof(credentials), static_cast("Authorization: Basic %s\r\n"), static_cast(encodedCredentials)); // Add the encoded credentials to the server request strncat(static_cast(serverRequest), static_cast(credentials), this->bufferSize); strncat(static_cast(serverRequest), static_cast("\r\n"), this->bufferSize); std::cout << static_cast("serverRequest size: ") << strlen(serverRequest) << static_cast(" of ") << this->bufferSize << static_cast(" bytes available") << std::endl; // Send the server request std::cout << "Sending server request: " << serverRequest << std::endl; this->ntripClient->write(serverRequest, strlen(serverRequest)); // Wait up to 5 seconds for response const uint32_t lastMillis = millis(); while (static_cast(!ntripClient->available())) { if (millis() - lastMillis > this->timeOut) { std::cout << "Caster timed out!" << std::endl; this->ntripClient->stop(); return false; } delay(10); } // Check reply uint16_t httpStatusCode = 0; char response[this->bufferSize]; uint16_t responseIndex = 0; while (static_cast(this->ntripClient->available())) { if (responseIndex == sizeof(response)) { break; } response[responseIndex++] = ntripClient->read(); if (httpStatusCode == 0) { if (strstr(response, static_cast("200")) != nullptr) { httpStatusCode = httpCheck; } if (strstr(response, static_cast("401")) != nullptr) { httpStatusCode = httpError; } } } response[responseIndex] = '\0'; // std::cout << "Caster response: " << response << std::endl; if (httpStatusCode != httpCheck) { std::cout << "Failed to connect to " << this->host << " - HTTP Code: " << (int)httpStatusCode << " Length of Response: " << responseIndex << std::endl; if (httpStatusCode == 0) { std::cout << "Response: " << response << std::endl; } else if (httpStatusCode == httpError) { std::cout << "Statuscode 401 - Unauthorized" << std::endl; } return false; } std::cout << "Connected to: " << this->host << std::endl; this->lastReceivedRtcmTime = millis(); return true; } void NTRIPClient::closeConnection() { if (static_cast(this->ntripClient->connected())) { this->ntripClient->stop(); } this->activated = false; std::cout << "NtripClient disconnected from: " << this->host << std::endl; } bool NTRIPClient::processConnection() { if (static_cast(this->ntripClient->connected())) { uint8_t rtcmData[this->bufferSize * 8]; uint16_t rtcmCount = 0; while (static_cast(this->ntripClient->available())) { rtcmData[rtcmCount++] = ntripClient->read(); if (rtcmCount == sizeof(rtcmData)) { break; } } if (rtcmCount > 0) { this->lastReceivedRtcmTime = millis(); this->gps->pushRawData(static_cast(rtcmData), rtcmCount); // std::cout << "Pushed " << rtcmCount << " RTCM bytes to ZED." << std::endl; } } else { std::cout << "Connection to " << this->host << " dropped!" << std::endl; return false; } if (millis() - this->lastReceivedRtcmTime > this->timeOut) { std::cout << "RTCM timeout!" << std::endl; return false; } return true; } void NTRIPClient::checkAutoReconnect() { if (!this->autoReconnect) { return; } if (millis() - this->lastReconnectTime < this->reconnectDelayTime) { return; } this->lastReconnectTime = millis(); if (this->reconnectAttemps >= this->maxReconnectAttemps) { this->autoReconnect = false; return; } this->activated = true; this->reconnectAttemps++; } void NTRIPClient::pushGPGGA() { if (!this->transmitLocation && !this->activated) { return; } if (millis() - this->lastGPGGAPushTime < this->pushGPGGATime) { return; } this->lastGPGGAPushTime = millis(); if (!this->ntripClient->connected()) { std::cout << "Failed to pushing GGA to server: " << std::endl; } auto *data = new NMEA_GGA_data_t; const uint8_t res = this->gps->getLatestNMEAGPGGA(data); if (res == 2) { this->ntripClient->print(reinterpret_cast(data)); } delete data; } bool NTRIPClient::isConnected() { return this->state == NTRIPClientStates::pushData; }