From f6228ff7b2c720cbc08acb4f411265faab923237 Mon Sep 17 00:00:00 2001 From: Scott Brust <scott.brust@particle.io> Date: Thu, 28 Dec 2023 11:52:14 -0800 Subject: [PATCH 1/5] [msom] Add GNSS test FQC command --- user/applications/tinker/src/burnin_test.cpp | 50 ++++-- user/applications/tinker/src/burnin_test.h | 1 + user/applications/tinker/src/fqc_test.cpp | 175 ++++++++++++++++++- user/applications/tinker/src/fqc_test.h | 14 +- 4 files changed, 225 insertions(+), 15 deletions(-) diff --git a/user/applications/tinker/src/burnin_test.cpp b/user/applications/tinker/src/burnin_test.cpp index a47e3e87a7..013f5bfaca 100644 --- a/user/applications/tinker/src/burnin_test.cpp +++ b/user/applications/tinker/src/burnin_test.cpp @@ -557,7 +557,18 @@ static int callbackGPSGGA(int type, const char* buf, int len, bool* gnssLocked) return 1; } -bool BurninTest::testGnss() { +static int callbackQGPS(int type, const char* buf, int len, bool* cmdSuccess) { + //Log.trace("%d : %s", strlen(buf), buf); + + // If string is `OK` or `+CME ERROR: 504` (ie GNSS already started) then GPS engine is enabled + if (!strcmp(buf, "\r\n+CME ERROR: 504\r\n") || !strcmp(buf, "\r\nOK\r\n")) { + *cmdSuccess = true; + } + return 0; +} + + +bool BurninTest::initGnss() { // Turn on GNSS + Modem pinMode(GNSS_ANT_PWR, OUTPUT); digitalWrite(GNSS_ANT_PWR, HIGH); @@ -568,25 +579,40 @@ bool BurninTest::testGnss() { // Enable GNSS. It can take some time after the modem AT interface comes up for the GNSS engine to start const int RETRIES = 10; - int r = 0; - for (int i = 0; i < RETRIES && r != RESP_OK; i++) { - r = Cellular.command("AT+QGPS=1\r\n"); + + bool success = false; + for (int i = 0; i < RETRIES && !success; i++) { + Cellular.command(callbackQGPS, &success, 5000, "AT+QGPS=1"); delay(1000); } - if (r != RESP_OK) { - strcpy(BurninErrorMessage, "AT+QGPS=1 failed, GNSS not enabled"); + if (!success) { + Log.error("AT+QGPS=1 failed, GNSS not enabled"); return false; } - // Configure antenna for GNSS priority - for (int i = 0; i < RETRIES && r != RESP_OK; i++) { - r = Cellular.command("AT+QGPSCFG=\"priority\",0"); - delay(1000); + hal_device_hw_info deviceInfo = {}; + hal_get_device_hw_info(&deviceInfo, nullptr); + if (deviceInfo.ncp[0] == PLATFORM_NCP_QUECTEL_BG95_M5) { + int r = 0; + // Configure antenna for GNSS priority + for (int i = 0; i < RETRIES && r != RESP_OK; i++) { + r = Cellular.command("AT+QGPSCFG=\"priority\",0"); + delay(1000); + } + + if (r != RESP_OK) { + Log.error("AT+QGPSCFG=\"priority\",0 failed, GNSS not prioritized"); + return false; + } } - if (r != RESP_OK) { - strcpy(BurninErrorMessage, "AT+QGPSCFG=\"priority\",0 failed, GNSS not prioritized"); + return true; +} + +bool BurninTest::testGnss() { + if (!initGnss()) { + strcpy(BurninErrorMessage, "Failed to initialize GNSS"); return false; } diff --git a/user/applications/tinker/src/burnin_test.h b/user/applications/tinker/src/burnin_test.h index 3f9755426b..3ca7a4db32 100644 --- a/user/applications/tinker/src/burnin_test.h +++ b/user/applications/tinker/src/burnin_test.h @@ -14,6 +14,7 @@ class BurninTest { void setup(bool forceEnable = false); void loop(); + bool initGnss(); enum class BurninTestState : uint32_t { NONE, diff --git a/user/applications/tinker/src/fqc_test.cpp b/user/applications/tinker/src/fqc_test.cpp index 6060f14481..9358afe11e 100644 --- a/user/applications/tinker/src/fqc_test.cpp +++ b/user/applications/tinker/src/fqc_test.cpp @@ -53,7 +53,7 @@ namespace { FqcTest::FqcTest() : writer((char *)json_response_buffer, sizeof(json_response_buffer)), tcpClient(), - inited_(true) { + inited_(true) { // TODO: Other member vars memset(json_response_buffer, 0x00, sizeof(json_response_buffer)); } @@ -93,6 +93,8 @@ bool FqcTest::process(JSONValue test){ return wifiNetcat(test); } else if (has(test, "WIFI_SCAN_NETWORKS")) { return wifiScanNetworks(test); + } else if (has(test, "GNSS_TEST")) { + return gnssTest(test); } else if (has(test, "BURNIN_TEST")) { BurninTest::instance()->setup(true); return true; @@ -101,9 +103,15 @@ bool FqcTest::process(JSONValue test){ return false; } -bool FqcTest::passResponse(bool success) { +bool FqcTest::passResponse(bool success, String message, int errorCode) { writer.beginObject(); writer.name("pass").value(success); + if (message.length()) { + writer.name("message").value(message); + } + if (errorCode) { + writer.name("errorCode").value(errorCode); + } writer.endObject(); return true; } @@ -507,6 +515,169 @@ bool FqcTest::wifiScanNetworks(JSONValue req) { return true; } +#if PLATFORM_ID == PLATFORM_MSOM +static int callbackGPSGSV(int type, const char* buf, int len, FqcTest* self) { + // EXAMPLE: + // $<TalkerID>GSV,<TotalNumSen>,<SenNum>,<TotalNumSat> + // {,<SatID>,<SatElev>,<SatAz>,<SatCN0>}, + // <SignalID>*<Checksum><CR><LF> + + // Single sat: + // > AT+QGPSGNMEA="GSV" + // < +QGPSGNMEA: $GPGSV,1,1,01,30,,,32,1*67 + // < OK + + // Multiples: + // > AT+QGPSGNMEA="GSV" + // < +QGPSGNMEA: $GPGSV,3,1,11,30,46,293,32,04,23,134,00,05,11,319,00,07,68,346,00,1*6B + // < +QGPSGNMEA: $GPGSV,3,2,11,08,37,096,00,09,61,141,00,14,33,221,00,16,01,042,00,1*68 + // < +QGPSGNMEA: $GPGSV,3,3,11,20,23,285,00,22,13,218,00,27,21,053,00,1*51 + // < +QGPSGNMEA: $GLGSV,3,1,09,78,59,010,00,86,,,00,77,08,046,00,80,01,239,00,1*44 + // < +QGPSGNMEA: $GLGSV,3,2,09,79,48,263,00,69,44,322,00,88,02,103,00,87,08,055,00,1*7E + // < +QGPSGNMEA: $GLGSV,3,3,09,67,24,162,00,1*43 + // < OK + + // GL = Glonass + // GP = GPS + // PQ = BeiDou + // GA = Galileo + + //Log.trace("%d : %s", strlen(buf), buf); + + // If this is the trailing OK response, exit + if(!strcmp(buf, "\r\nOK\r\n")) { + return 0; + } + + const int MAX_GSV_STR_LEN = 128; + char gsvSentence[MAX_GSV_STR_LEN] = {}; + strlcpy(gsvSentence, buf, MAX_GSV_STR_LEN); + + const char * delimiters = ", "; + char * token = strtok(gsvSentence, delimiters); + int i = 1; + while (token) { + //Log.trace("%d %s", i, token); + token = strtok(NULL, delimiters); + i++; + + switch (i) { + case 5: // TotalNumSat + { + int numberSattelites = atoi(token); + //Log.info("numberSattelites %d", numberSattelites); + if (numberSattelites > 0) { + self->gnssSatelliteCount_ = numberSattelites; + } + break; + } + // TODO: parse/store SatCN0 + default: + break; + } + } + + // Ask for more GSV lines from the AT parser + return WAIT; +} + +void FqcTest::gnssLoop(void* arg) { + FqcTest* self = static_cast<FqcTest*>(arg); + + hal_device_hw_info deviceInfo = {}; + hal_get_device_hw_info(&deviceInfo, nullptr); + bool isBG95 = deviceInfo.ncp[0] == PLATFORM_NCP_QUECTEL_BG95_M5; + + while(true) + { + if(self->gnssEnableSearch_) { + auto timeout = millis() + self->gnssPollTimeoutMs_; + + if (isBG95) { + Cellular.command("AT+QGPSCFG=\"priority\",0"); + } + + while (millis() < timeout && !self->gnssSatelliteCount_) { + Cellular.command(callbackGPSGSV, self, 1000, "AT+QGPSGNMEA=\"GSV\""); + Log.info("count %lu", self->gnssSatelliteCount_); + delay(1000); + } + self->gnssEnableSearch_ = false; + + if (isBG95) { + Cellular.command("AT+QGPSCFG=\"priority\",1"); + } + } + delay(1000); + } +} + +bool FqcTest::gnssTest(JSONValue req) { + + auto parameters = get(req, "GNSS_TEST"); + + if (parameters.isValid()) { + auto command = String(getValue(parameters, "command").toString()); + auto timeout = getValue(parameters, "timeout").toInt(); + + if (command != nullptr) { + Log.info("GNSS test command: %s", command.c_str()); + } + + gnssPollTimeoutMs_ = timeout ? timeout * 1000 : GNSS_POLL_TIMEOUT_DEFAULT_MS; + Log.info("GNSS test timeout: %lu", gnssPollTimeoutMs_); + + if (command == String("start")) { + if (!gnssThread_) { + gnssSatelliteCount_ = 0; + gnssEnableSearch_ = true; + + BurninTest::instance()->initGnss(); + gnssThread_ = new Thread("gnss-test", gnssLoop, this, OS_THREAD_PRIORITY_DEFAULT); + SPARK_ASSERT(gnssThread_); + + passResponse(true); + } else if(gnssEnableSearch_) { + auto warning = "Already searching for signal"; + Log.warn(warning); + passResponse(false, warning); + } else { + gnssSatelliteCount_ = 0; + gnssEnableSearch_ = true; + passResponse(true); + } + } else if (command == String("status")) { + + Log.info("gnssSatelliteCount_ %lu", gnssSatelliteCount_); + + if (!gnssThread_) { + Log.warn("Test not started"); + passResponse(false, "Test not started"); + } + else if (gnssEnableSearch_) { + Log.info("Pending"); + passResponse(false, "Test in progress", SYSTEM_ERROR_BUSY); + } else if(gnssSatelliteCount_ == 0) { + Log.info("Timed out"); + passResponse(false, "No satellite signal detected before timeout", SYSTEM_ERROR_TIMEOUT); + } else { + Log.info("Success"); + String successMessage = String("Found ") + gnssSatelliteCount_ + String(" satellites"); + passResponse(true, successMessage); + } + } else { + passResponse(false, String("Unrecognized Command: ") + command); + } + } + + return true; +} +#else +bool FqcTest::gnssTest(JSONValue req) { + return passResponse(false, "Platform does not support gnss test"); +} +#endif // PLATFORM_ID == PLATFORM_MSOM + } #endif // HAL_PLATFORM_RTL872X diff --git a/user/applications/tinker/src/fqc_test.h b/user/applications/tinker/src/fqc_test.h index 0862934d52..0df4a81c1a 100644 --- a/user/applications/tinker/src/fqc_test.h +++ b/user/applications/tinker/src/fqc_test.h @@ -21,6 +21,8 @@ class FqcTest { char * reply(); size_t replySize(); + uint32_t gnssSatelliteCount_ = 0; + private: void initWriter(); @@ -30,10 +32,19 @@ class FqcTest { char json_response_buffer[2048]; TCPClient tcpClient; bool inited_; + + Thread* gnssThread_; + static const uint32_t GNSS_POLL_TIMEOUT_DEFAULT_MS = 30000; + uint32_t gnssPollTimeoutMs_; + std::atomic_bool gnssEnableSearch_; + + // TODO: Vector for each type of sattelite? - bool passResponse(bool success); + bool passResponse(bool success, String message = String(), int errorCode = 0); bool tcpErrorResponse(int tcpError); + static void gnssLoop(void* arg); + void parseIpAndPort(JSONValue parameters); int sendTCPMessage(const char * tx_data, char * rx_data_buffer, int rx_data_buffer_length, int response_poll_ms = 5000); @@ -43,6 +54,7 @@ class FqcTest { bool ioTest(JSONValue req); bool wifiNetcat(JSONValue req); bool wifiScanNetworks(JSONValue req); + bool gnssTest(JSONValue req); }; } // namespace particle From 1eed3cd0974af5e6149c0b0a8942e2d011d253da Mon Sep 17 00:00:00 2001 From: Scott Brust <scott.brust@particle.io> Date: Thu, 28 Dec 2023 14:59:15 -0800 Subject: [PATCH 2/5] fixup / rename / Ensure gps engine enabled for bg95 --- user/applications/tinker/src/fqc_test.cpp | 106 +++++++++++----------- user/applications/tinker/src/fqc_test.h | 15 ++- 2 files changed, 61 insertions(+), 60 deletions(-) diff --git a/user/applications/tinker/src/fqc_test.cpp b/user/applications/tinker/src/fqc_test.cpp index 9358afe11e..cdc73a5fb8 100644 --- a/user/applications/tinker/src/fqc_test.cpp +++ b/user/applications/tinker/src/fqc_test.cpp @@ -51,10 +51,11 @@ namespace { } FqcTest::FqcTest() : - writer((char *)json_response_buffer, sizeof(json_response_buffer)), - tcpClient(), - inited_(true) { // TODO: Other member vars - memset(json_response_buffer, 0x00, sizeof(json_response_buffer)); + writer_((char *)json_response_buffer_, sizeof(json_response_buffer_)), + tcpClient_(), + inited_(true), + gnssEnableSearch_(false) { + memset(json_response_buffer_, 0x00, sizeof(json_response_buffer_)); } FqcTest::~FqcTest() { @@ -66,16 +67,16 @@ FqcTest* FqcTest::instance() { } char * FqcTest::reply() { - return writer.buffer(); + return writer_.buffer(); } size_t FqcTest::replySize() { - return writer.dataSize(); + return writer_.dataSize(); } void FqcTest::initWriter(){ - memset(json_response_buffer, 0x00, sizeof(json_response_buffer)); - writer = JSONBufferWriter((char *)json_response_buffer, sizeof(json_response_buffer)); + memset(json_response_buffer_, 0x00, sizeof(json_response_buffer_)); + writer_ = JSONBufferWriter((char *)json_response_buffer_, sizeof(json_response_buffer_)); } bool FqcTest::process(JSONValue test){ @@ -104,22 +105,22 @@ bool FqcTest::process(JSONValue test){ } bool FqcTest::passResponse(bool success, String message, int errorCode) { - writer.beginObject(); - writer.name("pass").value(success); + writer_.beginObject(); + writer_.name("pass").value(success); if (message.length()) { - writer.name("message").value(message); + writer_.name("message").value(message); } if (errorCode) { - writer.name("errorCode").value(errorCode); + writer_.name("errorCode").value(errorCode); } - writer.endObject(); + writer_.endObject(); return true; } bool FqcTest::tcpErrorResponse(int tcpError) { - writer.beginObject(); - writer.name("pass").value(false); - writer.name("errorCode").value(tcpError); + writer_.beginObject(); + writer_.name("pass").value(false); + writer_.name("errorCode").value(tcpError); String errorMessage; if(tcpError == SYSTEM_ERROR_NETWORK){ errorMessage = "Wifi not ready"; @@ -137,8 +138,8 @@ bool FqcTest::tcpErrorResponse(int tcpError) { errorMessage = "Netcat command parameters malformed"; } - writer.name("message").value(errorMessage); - writer.endObject(); + writer_.name("message").value(errorMessage); + writer_.endObject(); return true; } @@ -157,8 +158,8 @@ void FqcTest::parseIpAndPort(JSONValue parameters) { } // Store FQC station port/ip - this->tcpPort = port; - memcpy(this->tcpServer, server, sizeof(server)); + this->tcpPort_ = port; + memcpy(this->tcpServer_, server, sizeof(server)); } int FqcTest::sendTCPMessage(const char * tx_data, char * rx_data_buffer, int rx_data_buffer_length, int response_poll_ms) { @@ -169,9 +170,9 @@ int FqcTest::sendTCPMessage(const char * tx_data, char * rx_data_buffer, int rx_ return SYSTEM_ERROR_NETWORK; } - if (!tcpClient.connected()) { - if (tcpClient.connect(this->tcpServer, this->tcpPort)) { - Log.info("TCP client connected to %s", tcpClient.remoteIP().toString().c_str()); + if (!tcpClient_.connected()) { + if (tcpClient_.connect(this->tcpServer_, this->tcpPort_)) { + Log.info("TCP client connected to %s", tcpClient_.remoteIP().toString().c_str()); } else { Log.error("TCP client connection failed"); @@ -179,18 +180,18 @@ int FqcTest::sendTCPMessage(const char * tx_data, char * rx_data_buffer, int rx_ } } - if (tcpClient.connected()) { + if (tcpClient_.connected()) { Log.info("Sending message: %s len %d", tx_data, strlen(tx_data)); - tcpClient.write((uint8_t *)tx_data, strlen(tx_data)); + tcpClient_.write((uint8_t *)tx_data, strlen(tx_data)); // Poll for response int delay_period_ms = 500; - for(int i = 0; i < (response_poll_ms / delay_period_ms) && !tcpClient.available(); i++){ + for(int i = 0; i < (response_poll_ms / delay_period_ms) && !tcpClient_.available(); i++){ delay(delay_period_ms); } - while(tcpClient.available() && (bytesRead < rx_data_buffer_length)) { - rx_data_buffer[bytesRead++] = tcpClient.read(); + while(tcpClient_.available() && (bytesRead < rx_data_buffer_length)) { + rx_data_buffer[bytesRead++] = tcpClient_.read(); } if(bytesRead) { @@ -203,7 +204,7 @@ int FqcTest::sendTCPMessage(const char * tx_data, char * rx_data_buffer, int rx_ return SYSTEM_ERROR_TIMEOUT; } - tcpClient.stop(); + tcpClient_.stop(); } return bytesRead; @@ -356,10 +357,10 @@ bool FqcTest::ioTest(JSONValue req) { if (result != SYSTEM_ERROR_NONE) { Log.error("Could not read logical efuse"); - writer.beginObject(); - writer.name("pass").value(false); - writer.name("message").value("could not read logical efuse"); - writer.endObject(); + writer_.beginObject(); + writer_.name("pass").value(false); + writer_.name("message").value("could not read logical efuse"); + writer_.endObject(); return true; } else { Log.info("Hardware Model: 0x%X Variant: %lu", (unsigned int)model, variant); @@ -410,13 +411,13 @@ bool FqcTest::ioTest(JSONValue req) { passResponse(true); } else { - writer.beginObject(); - writer.name("pass").value(false); - writer.name("pinA").value(pinNumberToPinName(pinA)); - writer.name("pinB").value(pinNumberToPinName(pinB)); - writer.name("errorPin").value(pinNumberToPinName(errorPin)); - writer.name("message").value(failedTest); - writer.endObject(); + writer_.beginObject(); + writer_.name("pass").value(false); + writer_.name("pinA").value(pinNumberToPinName(pinA)); + writer_.name("pinB").value(pinNumberToPinName(pinB)); + writer_.name("errorPin").value(pinNumberToPinName(errorPin)); + writer_.name("message").value(failedTest); + writer_.endObject(); } return true; @@ -494,24 +495,24 @@ bool FqcTest::wifiScanNetworks(JSONValue req) { } // Serialize to JSON and return this list over USB, like the regular WIFI scan does - writer.beginObject(); - writer.name("pass").value(true); - writer.name("networks").beginArray(); + writer_.beginObject(); + writer_.name("pass").value(true); + writer_.name("networks").beginArray(); for(WiFiAccessPoint& network: networks){ - writer.beginObject(); - writer.name("ssid").value(String(network.ssid, network.ssidLength)); + writer_.beginObject(); + writer_.name("ssid").value(String(network.ssid, network.ssidLength)); char bssidStr[32] = {}; sprintf(bssidStr, "%02X:%02X:%02X:%02X:%02X:%02X", network.bssid[0], network.bssid[1], network.bssid[2], network.bssid[3], network.bssid[4], network.bssid[5]); - writer.name("bssid").value(String(bssidStr)); + writer_.name("bssid").value(String(bssidStr)); - writer.name("security").value((int)network.security); - writer.name("channel").value(network.channel); - writer.name("rssi").value(network.rssi); - writer.endObject(); + writer_.name("security").value((int)network.security); + writer_.name("channel").value(network.channel); + writer_.name("rssi").value(network.rssi); + writer_.endObject(); } - writer.endArray(); - writer.endObject(); + writer_.endArray(); + writer_.endObject(); return true; } @@ -594,6 +595,7 @@ void FqcTest::gnssLoop(void* arg) { auto timeout = millis() + self->gnssPollTimeoutMs_; if (isBG95) { + Cellular.command("AT+QGPS=1"); Cellular.command("AT+QGPSCFG=\"priority\",0"); } diff --git a/user/applications/tinker/src/fqc_test.h b/user/applications/tinker/src/fqc_test.h index 0df4a81c1a..7833e1f2ec 100644 --- a/user/applications/tinker/src/fqc_test.h +++ b/user/applications/tinker/src/fqc_test.h @@ -24,21 +24,20 @@ class FqcTest { uint32_t gnssSatelliteCount_ = 0; private: + static const uint32_t GNSS_POLL_TIMEOUT_DEFAULT_MS = 30000; + void initWriter(); - uint8_t tcpServer[4]; - int tcpPort; - JSONBufferWriter writer; - char json_response_buffer[2048]; - TCPClient tcpClient; + uint8_t tcpServer_[4]; + int tcpPort_; + JSONBufferWriter writer_; + char json_response_buffer_[2048]; + TCPClient tcpClient_; bool inited_; Thread* gnssThread_; - static const uint32_t GNSS_POLL_TIMEOUT_DEFAULT_MS = 30000; uint32_t gnssPollTimeoutMs_; std::atomic_bool gnssEnableSearch_; - - // TODO: Vector for each type of sattelite? bool passResponse(bool success, String message = String(), int errorCode = 0); bool tcpErrorResponse(int tcpError); From 8c3e4eb794a305b60e26255abcef1d3f887104ef Mon Sep 17 00:00:00 2001 From: Scott Brust <scott.brust@particle.io> Date: Thu, 4 Jan 2024 11:12:18 -0800 Subject: [PATCH 3/5] fixup spelling --- user/applications/tinker/src/burnin_test.cpp | 8 ++++---- user/applications/tinker/src/fqc_test.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/user/applications/tinker/src/burnin_test.cpp b/user/applications/tinker/src/burnin_test.cpp index 013f5bfaca..d5500e917b 100644 --- a/user/applications/tinker/src/burnin_test.cpp +++ b/user/applications/tinker/src/burnin_test.cpp @@ -519,7 +519,7 @@ static int callbackGPSGGA(int type, const char* buf, int len, bool* gnssLocked) strlcpy(gpggaSentence, buf, MAX_GPGGA_STR_LEN); String lattitudeLongitude("LAT/LONG:"); - int numberSattelites = 0; + int numberSatellites = 0; const char * delimiters = ","; char * token = strtok(gpggaSentence, delimiters); @@ -543,10 +543,10 @@ static int callbackGPSGGA(int type, const char* buf, int len, bool* gnssLocked) lattitudeLongitude.concat(token); break; case 8: // Number satellites - numberSattelites = (int)String(token).toInt(); - if (numberSattelites > 0) { + numberSatellites = (int)String(token).toInt(); + if (numberSatellites > 0) { *gnssLocked = true; - Log.info("%s Satellites: %d", lattitudeLongitude.c_str(), numberSattelites); + Log.info("%s Satellites: %d", lattitudeLongitude.c_str(), numberSatellites); } break; default: diff --git a/user/applications/tinker/src/fqc_test.cpp b/user/applications/tinker/src/fqc_test.cpp index cdc73a5fb8..8023bdc27f 100644 --- a/user/applications/tinker/src/fqc_test.cpp +++ b/user/applications/tinker/src/fqc_test.cpp @@ -565,10 +565,10 @@ static int callbackGPSGSV(int type, const char* buf, int len, FqcTest* self) { switch (i) { case 5: // TotalNumSat { - int numberSattelites = atoi(token); - //Log.info("numberSattelites %d", numberSattelites); - if (numberSattelites > 0) { - self->gnssSatelliteCount_ = numberSattelites; + int numberSatellites = atoi(token); + //Log.info("numberSatellites %d", numberSatellites); + if (numberSatellites > 0) { + self->gnssSatelliteCount_ = numberSatellites; } break; } From bc6b2e14b9f4499016cdeba0f48855a7a4d39b16 Mon Sep 17 00:00:00 2001 From: Scott Brust <scott.brust@particle.io> Date: Fri, 5 Jan 2024 16:12:04 -0800 Subject: [PATCH 4/5] return full raw GSV sentences, simplify parsing --- user/applications/tinker/src/fqc_test.cpp | 57 ++++++++++------------- user/applications/tinker/src/fqc_test.h | 1 + 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/user/applications/tinker/src/fqc_test.cpp b/user/applications/tinker/src/fqc_test.cpp index 8023bdc27f..de528d5515 100644 --- a/user/applications/tinker/src/fqc_test.cpp +++ b/user/applications/tinker/src/fqc_test.cpp @@ -55,6 +55,7 @@ FqcTest::FqcTest() : tcpClient_(), inited_(true), gnssEnableSearch_(false) { + gnssGpsvStrings_ = String(); memset(json_response_buffer_, 0x00, sizeof(json_response_buffer_)); } @@ -545,37 +546,19 @@ static int callbackGPSGSV(int type, const char* buf, int len, FqcTest* self) { //Log.trace("%d : %s", strlen(buf), buf); - // If this is the trailing OK response, exit - if(!strcmp(buf, "\r\nOK\r\n")) { + // If this is the trailing OK response, exit from the parser + if (String(buf) == String("\r\nOK\r\n")) { return 0; } - const int MAX_GSV_STR_LEN = 128; - char gsvSentence[MAX_GSV_STR_LEN] = {}; - strlcpy(gsvSentence, buf, MAX_GSV_STR_LEN); + // Store the raw NMEA string + self->gnssGpsvStrings_.concat(buf); - const char * delimiters = ", "; - char * token = strtok(gsvSentence, delimiters); - int i = 1; - while (token) { - //Log.trace("%d %s", i, token); - token = strtok(NULL, delimiters); - i++; - - switch (i) { - case 5: // TotalNumSat - { - int numberSatellites = atoi(token); - //Log.info("numberSatellites %d", numberSatellites); - if (numberSatellites > 0) { - self->gnssSatelliteCount_ = numberSatellites; - } - break; - } - // TODO: parse/store SatCN0 - default: - break; - } + int numberSatellites = 0; + int ret = sscanf(buf,"\r\n+QGPSGNMEA: $%*5c,%*d,%*d,%d,", &numberSatellites); + Log.trace("ret = %d, numberSatellites: %d", ret, numberSatellites); + if (numberSatellites > 0) { + self->gnssSatelliteCount_ = numberSatellites; } // Ask for more GSV lines from the AT parser @@ -588,21 +571,30 @@ void FqcTest::gnssLoop(void* arg) { hal_device_hw_info deviceInfo = {}; hal_get_device_hw_info(&deviceInfo, nullptr); bool isBG95 = deviceInfo.ncp[0] == PLATFORM_NCP_QUECTEL_BG95_M5; + const uint32_t POLL_MS = 1000; + const uint32_t EXTRA_POLL_MS = 3000; while(true) { if(self->gnssEnableSearch_) { auto timeout = millis() + self->gnssPollTimeoutMs_; + bool extendSearch = true; if (isBG95) { Cellular.command("AT+QGPS=1"); Cellular.command("AT+QGPSCFG=\"priority\",0"); } - while (millis() < timeout && !self->gnssSatelliteCount_) { + while (millis() < timeout) { + self->gnssGpsvStrings_ = String(); Cellular.command(callbackGPSGSV, self, 1000, "AT+QGPSGNMEA=\"GSV\""); - Log.info("count %lu", self->gnssSatelliteCount_); - delay(1000); + delay(POLL_MS); + + // Run a bit longer after finding a satellite to see if we detect more + if (self->gnssSatelliteCount_ && extendSearch) { + timeout = millis() + EXTRA_POLL_MS; + extendSearch = false; + } } self->gnssEnableSearch_ = false; @@ -610,7 +602,7 @@ void FqcTest::gnssLoop(void* arg) { Cellular.command("AT+QGPSCFG=\"priority\",1"); } } - delay(1000); + delay(POLL_MS); } } @@ -664,8 +656,7 @@ bool FqcTest::gnssTest(JSONValue req) { passResponse(false, "No satellite signal detected before timeout", SYSTEM_ERROR_TIMEOUT); } else { Log.info("Success"); - String successMessage = String("Found ") + gnssSatelliteCount_ + String(" satellites"); - passResponse(true, successMessage); + passResponse(true, gnssGpsvStrings_); } } else { passResponse(false, String("Unrecognized Command: ") + command); diff --git a/user/applications/tinker/src/fqc_test.h b/user/applications/tinker/src/fqc_test.h index 7833e1f2ec..138a33eeb9 100644 --- a/user/applications/tinker/src/fqc_test.h +++ b/user/applications/tinker/src/fqc_test.h @@ -22,6 +22,7 @@ class FqcTest { size_t replySize(); uint32_t gnssSatelliteCount_ = 0; + String gnssGpsvStrings_; private: static const uint32_t GNSS_POLL_TIMEOUT_DEFAULT_MS = 30000; From 76e9ca29dee4ec0878ecc26a8f1acdef984287aa Mon Sep 17 00:00:00 2001 From: Scott Brust <scott.brust@particle.io> Date: Mon, 8 Jan 2024 15:54:30 -0800 Subject: [PATCH 5/5] Switch to GGA nmea output, require fix for fqc pass --- user/applications/tinker/src/fqc_test.cpp | 72 ++++++++--------------- user/applications/tinker/src/fqc_test.h | 5 +- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/user/applications/tinker/src/fqc_test.cpp b/user/applications/tinker/src/fqc_test.cpp index de528d5515..4c31223df2 100644 --- a/user/applications/tinker/src/fqc_test.cpp +++ b/user/applications/tinker/src/fqc_test.cpp @@ -55,7 +55,7 @@ FqcTest::FqcTest() : tcpClient_(), inited_(true), gnssEnableSearch_(false) { - gnssGpsvStrings_ = String(); + gnssNmeaOutput_ = String(); memset(json_response_buffer_, 0x00, sizeof(json_response_buffer_)); } @@ -519,31 +519,12 @@ bool FqcTest::wifiScanNetworks(JSONValue req) { #if PLATFORM_ID == PLATFORM_MSOM static int callbackGPSGSV(int type, const char* buf, int len, FqcTest* self) { - // EXAMPLE: - // $<TalkerID>GSV,<TotalNumSen>,<SenNum>,<TotalNumSat> - // {,<SatID>,<SatElev>,<SatAz>,<SatCN0>}, - // <SignalID>*<Checksum><CR><LF> - - // Single sat: - // > AT+QGPSGNMEA="GSV" - // < +QGPSGNMEA: $GPGSV,1,1,01,30,,,32,1*67 - // < OK - - // Multiples: - // > AT+QGPSGNMEA="GSV" - // < +QGPSGNMEA: $GPGSV,3,1,11,30,46,293,32,04,23,134,00,05,11,319,00,07,68,346,00,1*6B - // < +QGPSGNMEA: $GPGSV,3,2,11,08,37,096,00,09,61,141,00,14,33,221,00,16,01,042,00,1*68 - // < +QGPSGNMEA: $GPGSV,3,3,11,20,23,285,00,22,13,218,00,27,21,053,00,1*51 - // < +QGPSGNMEA: $GLGSV,3,1,09,78,59,010,00,86,,,00,77,08,046,00,80,01,239,00,1*44 - // < +QGPSGNMEA: $GLGSV,3,2,09,79,48,263,00,69,44,322,00,88,02,103,00,87,08,055,00,1*7E - // < +QGPSGNMEA: $GLGSV,3,3,09,67,24,162,00,1*43 + // > AT+QGPSGNMEA="GGA" + // < +QGPSGNMEA: $GPGGA,233906.00,3804.385678,N,12209.936243,W,1,05,1.4,149.0,M,-25.0,M,,*5C // < OK - // GL = Glonass - // GP = GPS - // PQ = BeiDou - // GA = Galileo - + // $<TalkerID>GGA,<UTC>,<Lat>,<N/S>,<Lon>,<E/W>,<Quality>,<NumSatUsed>,<HDOP>,<Alt>,M,<Sep>,M,<DiffAge>,<DiffStation>*<Checksum><CR><LF> + //Log.trace("%d : %s", strlen(buf), buf); // If this is the trailing OK response, exit from the parser @@ -552,13 +533,13 @@ static int callbackGPSGSV(int type, const char* buf, int len, FqcTest* self) { } // Store the raw NMEA string - self->gnssGpsvStrings_.concat(buf); + self->gnssNmeaOutput_.concat(buf); - int numberSatellites = 0; - int ret = sscanf(buf,"\r\n+QGPSGNMEA: $%*5c,%*d,%*d,%d,", &numberSatellites); - Log.trace("ret = %d, numberSatellites: %d", ret, numberSatellites); - if (numberSatellites > 0) { - self->gnssSatelliteCount_ = numberSatellites; + int fixQuality = 0; + int ret = sscanf(buf,"\r\n+QGPSGNMEA: $%*5c,%*d.%*d,%*d.%*d,%*c,%*d.%*d,%*c,%d,", &fixQuality); + Log.trace("ret = %d, fixQuality: %d", ret, fixQuality); + if (fixQuality > 0) { + self->gnssFixQuality_ = fixQuality; } // Ask for more GSV lines from the AT parser @@ -572,28 +553,25 @@ void FqcTest::gnssLoop(void* arg) { hal_get_device_hw_info(&deviceInfo, nullptr); bool isBG95 = deviceInfo.ncp[0] == PLATFORM_NCP_QUECTEL_BG95_M5; const uint32_t POLL_MS = 1000; - const uint32_t EXTRA_POLL_MS = 3000; while(true) { if(self->gnssEnableSearch_) { - auto timeout = millis() + self->gnssPollTimeoutMs_; - bool extendSearch = true; + auto startMillis = millis(); + auto timeout = startMillis + self->gnssPollTimeoutMs_; if (isBG95) { Cellular.command("AT+QGPS=1"); Cellular.command("AT+QGPSCFG=\"priority\",0"); } - while (millis() < timeout) { - self->gnssGpsvStrings_ = String(); - Cellular.command(callbackGPSGSV, self, 1000, "AT+QGPSGNMEA=\"GSV\""); + while (millis() < timeout && !self->gnssFixQuality_ ) { + self->gnssNmeaOutput_ = String(); + Cellular.command(callbackGPSGSV, self, 1000, "AT+QGPSGNMEA=\"GGA\""); delay(POLL_MS); - // Run a bit longer after finding a satellite to see if we detect more - if (self->gnssSatelliteCount_ && extendSearch) { - timeout = millis() + EXTRA_POLL_MS; - extendSearch = false; + if (self->gnssFixQuality_) { + self->gnssTimeToFix_ = millis() - startMillis; } } self->gnssEnableSearch_ = false; @@ -623,7 +601,7 @@ bool FqcTest::gnssTest(JSONValue req) { if (command == String("start")) { if (!gnssThread_) { - gnssSatelliteCount_ = 0; + gnssFixQuality_ = 0; gnssEnableSearch_ = true; BurninTest::instance()->initGnss(); @@ -636,13 +614,13 @@ bool FqcTest::gnssTest(JSONValue req) { Log.warn(warning); passResponse(false, warning); } else { - gnssSatelliteCount_ = 0; + gnssFixQuality_ = 0; gnssEnableSearch_ = true; passResponse(true); } } else if (command == String("status")) { - Log.info("gnssSatelliteCount_ %lu", gnssSatelliteCount_); + Log.info("gnssFixQuality_ %lu", gnssFixQuality_); if (!gnssThread_) { Log.warn("Test not started"); @@ -651,12 +629,12 @@ bool FqcTest::gnssTest(JSONValue req) { else if (gnssEnableSearch_) { Log.info("Pending"); passResponse(false, "Test in progress", SYSTEM_ERROR_BUSY); - } else if(gnssSatelliteCount_ == 0) { + } else if(gnssFixQuality_ == 0) { Log.info("Timed out"); - passResponse(false, "No satellite signal detected before timeout", SYSTEM_ERROR_TIMEOUT); + passResponse(false, "No fix detected before timeout", SYSTEM_ERROR_TIMEOUT); } else { - Log.info("Success"); - passResponse(true, gnssGpsvStrings_); + Log.info("Success, time to fix: %lu", gnssTimeToFix_); + passResponse(true, gnssNmeaOutput_); } } else { passResponse(false, String("Unrecognized Command: ") + command); diff --git a/user/applications/tinker/src/fqc_test.h b/user/applications/tinker/src/fqc_test.h index 138a33eeb9..d88b917f7d 100644 --- a/user/applications/tinker/src/fqc_test.h +++ b/user/applications/tinker/src/fqc_test.h @@ -21,8 +21,8 @@ class FqcTest { char * reply(); size_t replySize(); - uint32_t gnssSatelliteCount_ = 0; - String gnssGpsvStrings_; + uint32_t gnssFixQuality_ = 0; + String gnssNmeaOutput_; private: static const uint32_t GNSS_POLL_TIMEOUT_DEFAULT_MS = 30000; @@ -39,6 +39,7 @@ class FqcTest { Thread* gnssThread_; uint32_t gnssPollTimeoutMs_; std::atomic_bool gnssEnableSearch_; + uint32_t gnssTimeToFix_; bool passResponse(bool success, String message = String(), int errorCode = 0); bool tcpErrorResponse(int tcpError);