// SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors // // This file is part of WII5 Buoy firmware. // See LICENSE for full terms. /** * @file WII5SerialManager.cpp * @brief Serial-port helper: AT-response parsing, command queue. */ /* WII5SerialManager - Base class for managed serial ports Sketch uses 106588 bytes (41%) of program storage space. Maximum is 258048 bytes. Global variables use 5621 bytes (68%) of dynamic memory, leaving 2571 bytes for local variables. Maximum is 8192 bytes. Global variables use 5621 bytes (68%) of dynamic memory, leaving 2571 bytes for local variables. Maximum is 8192 bytes. */ #include #include #include void WII5SerialManager::sendNewLine() { stream->print(F("\r\n")); } void WII5SerialManager::beginSerialManager() { // console.log(LOG_DEBUG, F("SerialManager: Buffer size=%d"), bufMax); processTimeout = 15000; // TODO This should be configurable } void WII5SerialManager::endSerialManager() { // closeFile(); } // No disable ATM void WII5SerialManager::enableNmea() { gps = new TinyGPSPlus(); } void WII5SerialManager::setPassthrough(bool in) { passthrough = in; } bool WII5SerialManager::getPassthrough() { return passthrough; } void WII5SerialManager::setDebug(bool in) { debug = in; } bool WII5SerialManager::getDebug() { return debug; } void WII5SerialManager::setCapture(bool in) { capture = false; } bool WII5SerialManager::getCapture() { return capture; } void WII5SerialManager::setRecords(uint32_t in) { recordTotal = in; } uint32_t WII5SerialManager::getRecords() { return recordTotal; } void WII5SerialManager::setTimeout(uint32_t in) { processTimeout = in; } uint32_t WII5SerialManager::getTimeout() { return processTimeout; } // TODO - Make this reusable - e.g. plug in different list of lines (callback?) // Before calling: // - probramCount (where to start), -programTotal (where to end) (not length) // - sendCount = 0 WII5_SERIALCMDS WII5SerialManager::sendAll(WII5SERIAL_LAST lastUntil) { // Sent everyting - spin on if (programCount > programTotal) { return WII5SERIALCMD_OK; } // First count - read line to buffer if (sendCount == 0) { programLine(programCount); } switch (sendAndWait()) { // Still waiting. case WII5SERIALCMD_WAITING: if (debug && ((sendCount % 10000) == 0)) console.log(LOG_DEBUG, F("SERIAL: waiting, sendCount=%lu"), sendCount); break; // Allow these to fall through - we will spin on lines even if error case WII5SERIALCMD_TIMEOUT: console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending: %s"), buffer); programCount++; // NOTE - Just spin on for now sendCount = 0; // TODO - consier return WII5SERIALCMD_TIMEOUT; break; case WII5SERIALCMD_ERROR: console.log(LOG_ERROR, F("SERIAL: ERROR sending: %s"), buffer); programCount++; // NOTE - Just spin on for now sendCount = 0; // TODO - consier return WII5SERIALCMD_ERROR; break; case WII5SERIALCMD_OK: // TODO why? if (debug) { programLine(programCount); } programCount++; sendCount = 0; break; default: console.log(LOG_ERROR, F("SERIAL: Unknown sendAndWait return")); programCount++; // NOTE - Just spin on for now sendCount = 0; break; } return WII5SERIALCMD_WAITING; } // TODO return true when done - either timeout or error or success // - Instead could return: // CMD_WAITING - Stil waiting // CMD_OK - OK - success // CMD_ERROR - Error returned to us (see buffer) // CMD_TIMEOUT - Timeout WII5_SERIALCMDS WII5SerialManager::sendAndWait(WII5SERIAL_LAST lastUntil) { // First time in - send the string if (sendCount == 0) { // Send to IMU (this should allow for write, or print and with none, \r, \n or \r\n // stream->write(buffer, strlen(buffer)); stream->print(buffer); sendNewLine(); if (passthrough) consoleBuffer(); // if (debug) // console.log(LOG_DEBUG, F("SerialManager: send line: %s"), buffer); // Clear timeout processWait = 0; // Clear any incoming data last = WII5SERIALLAST_NONE; bufCount = 0; fieldLoc = 0; buffer[bufCount] = '\0'; processMode = WII5SERIALPARSER_OKHUH; } // Future times else { // Did we get OK or ERROR? (could this get into other modes - e.g. LINE or RECORD) if (last == lastUntil) { sendCount = 0; return WII5SERIALCMD_OK; } else if (last == WII5SERIALLAST_ERROR) { sendCount = 0; return WII5SERIALCMD_ERROR; } } if (processWait > processTimeout) { sendCount = 0; return WII5SERIALCMD_TIMEOUT; } // Increment counter and inform - WAITING sendCount++; return WII5SERIALCMD_WAITING; } // SerialManager Things: // - Turn Off // - Start now // - Start at X void WII5SerialManager::loop() { // Process the Serial - only if it is running - to consider this more... if (stream) { while (stream->available()) { // One line at a time - to allow proessing return data if (processSerial(stream->read())) { break; } } } } void WII5SerialManager::start(bool force) {} void WII5SerialManager::stop(bool force) {} void WII5SerialManager::setBuffer(char* buf, uint8_t sz) { buffer = buf; bufMax = sz; } void WII5SerialManager::setBaudrate(uint32_t baud) { console.log(LOG_WARN, F("Baudrate: Not supported by this module")); } void WII5SerialManager::repl(uint32_t baud) { uint8_t lastDot = 0; start(); if (baud > 0) setBaudrate(baud); if (!stream) { console.log(LOG_FATAL, F("SerialManager: Can not do REPL - no stream after start")); return; } while (1) { if (SerialConsole.available()) { char c = SerialConsole.read(); if (c == '.') lastDot++; else lastDot = 0; if (lastDot >= 3) { SerialConsole.flush(); stream->flush(); stop(); console.log(LOG_DEBUG, F("SerialManager: Completed REPL. User exit.")); return; } stream->write(c); } if (stream->available()) { char c = stream->read() ; SerialConsole.write(c); } } } void WII5SerialManager::processBufferLine() { if (debug) console.log(LOG_DEBUG, F("SerialManager: received line: %s"), buffer); last = WII5SERIALLAST_LINE; } void WII5SerialManager::processBufferOK() { if (bufCount < 1) return; // If last 2 chars = "ok" if ( (strncmp_P(buffer,(PGM_P) F("OK"), 2) == 0) || (strncmp_P(buffer,(PGM_P) F("ok"), 2) == 0) ) { if (debug) console.log(LOG_DEBUG, F("SerialManager: received ok: len=%d, str=%s"), bufCount, buffer); last = WII5SERIALLAST_OK; } // READY if ( (strncmp_P(buffer,(PGM_P) F("READY"), 5) == 0) || (strncmp_P(buffer,(PGM_P) F("ready"), 5) == 0) ) { if (debug) console.log(LOG_DEBUG, F("SerialManager: received ready: len=%d, str=%s"), bufCount, buffer); last = WII5SERIALLAST_READY; } if (strncmp_P(buffer,(PGM_P) F("+CSQ:"), 5) == 0) { lastVal = atol(&buffer[5]); if (debug) console.log(LOG_DEBUG, F("SerialManager: received CSQ: val=%lu, buffer=%s"), lastVal, buffer); last = WII5SERIALLAST_ATCSQ; } if (strncmp_P(buffer,(PGM_P) F("+SBDI:"), 6) == 0) { // +SBDI:,,,,, // MO status // 0 = No SBD message to send // 1 = SBD message successfully sent from the 9602 to the GSS // 2 = An error occurred while attempting to send SBD message from 9602 to GSS // MOMSN // Unique number of this message sent, Keep // MT status // 0 = No SBD message to receive from the GSS // 1 = SBD message successfully received from the GSS (how to get?) // 2 = An error occurred while attempting to perform a mailbox check or receive a message from the GSS. // MT length // The MT length is the length in bytes of the mobile terminated SBD message received from the GSS. If no message was received, this eld will be zero // MT queued // MT queued is a count of mobile terminated SBD messages waiting at the GSS to be transferred to the 9602 // // TODO Note - these need to be split on "," and stored in an array // ?recordVals[] // lastVal = atol(&buffer[4]); // Serial:< +SBDI: 1, 8, 0, 0, 0, 0 lastVal = atol(&buffer[5]); /* uint8_t lastMOStatus; uint16_t lastMOMSN; uint8_t lastMTStatus; uint16_t lastMTMSN; uint16_t lastMTLength; uint16_t lastMTQueued; */ if (debug) console.log(LOG_DEBUG, F("SerialManager: received SBDI: buffer=%s"), buffer); // +SBDI:,,,,, // Can't seem to ignore the spaces? // sscanf_P(&buffer[6], PSTR("%d,%d,%d,%d,%d,%d"), lastMSOStatus, lastMOMSN, lastMTStatus, lastMTMSN, lastMTLength, lastMTQueued); for(fieldCount = 0; fieldCount < WII5_FIELD_MAX; fieldCount++) { recordVals[fieldCount] = 0; } fieldCount = 0; p = strtok(&buffer[6], ","); while (p) { if (fieldCount < WII5_FIELD_MAX) { recordVals[fieldCount] = atol(p); p = strtok(NULL, ","); fieldCount++; } else { p = NULL; } } last = WII5SERIALLAST_ATSBDI; } if (strncmp_P(buffer,(PGM_P) F("BOOT Version:"), 13) == 0) { // TODO Note - these need to be split on "," and stored in an array // ?recordVals[] // lastVal = atol(&buffer[4]); if (debug) console.log(LOG_DEBUG, F("SerialManager: received BOOT Version: buffer=%s"), buffer); last = WII5SERIALLAST_BOOTVERSION; } // A number, but do we check the whole thing? if (buffer[0] == '3') { // lastVal = atol(&buffer[0]); // TODO Keeping this? Howto if (debug) console.log(LOG_DEBUG, F("SerialManager: received a number=%s"), buffer); last = WII5SERIALLAST_NUMBER; } /* if (debug) { if (bufCount == 0) console.log(LOG_DEBUG, F("SerialManager: failed ok: len=%d, str={NA}"), bufCount); else if (bufCount >= 2) console.log(LOG_DEBUG, F("SerialManager: failed ok: len-2=%c len-1=%c"), buffer[bufCount - 2], buffer[bufCount - 1]); else console.log(LOG_DEBUG, F("SerialManager: failed ok: len=%d, str=%s"), bufCount, buffer); } */ // ??? last = WII5SERIALLAST_OK; } // Sample Buffer Field - just assume longs void WII5SerialManager::processBufferField(char *buf, uint8_t len) { if (fieldCount < WII5_FIELD_MAX) { // Check size is valid if (len >= WII5SERIALMAANGER_FIELD_SIZE) { console.log(LOG_FATAL, F("SerialManager: processBufferField too large len=%d, available=%d"), int(len), int(WII5SERIALMAANGER_FIELD_SIZE)); return; } // Copy the data // TODO find all strncpy and do this !!! memset(fieldBuffer, 0, sizeof(fieldBuffer)); strncpy(fieldBuffer, buf, len); // Convert to Long recordVals[fieldCount] = atol(buf); fieldCount++; } } void WII5SerialManager::processRecord() { if (bufCount == 0) return; recordCount++; last = WII5SERIALLAST_RECORD; } // Basic line parsers bool WII5SerialManager::processSerial(char in) { // processMode // NONE, LINE, RECORD, OKHUH, AT if (processMode == WII5SERIALPARSER_NONE) return false; // TODO Untested binary1 mode - from Iridium.... if (processMode == WII5SERIALPARSER_BINARY1) { // TODO - maximum buffer size console.printf(F("# BINARY1 received = %d - %d\r\n"), bufCount, int(in)); binBuffer[bufCount] = in; bufCount++; if (binBufferExpect == 0) { // Look for length if ((bufCount > 1) && (binBuffer[bufCount - 2] == '\0')) { binBufferExpect = (uint8_t)binBuffer[bufCount - 1]; console.printf(F("# BINARY1 Expect = %d\r\n"), binBufferExpect); bufCount = 0; } // Hmm... too much guff... else if (bufCount > 25) { // Something went very wrong. processMode = WII5SERIALPARSER_NONE; console.printf(F("# BINARY1 No length provided\r\n")); return false; } } else if (bufCount >= (binBufferExpect + 2)) { processMode = WII5SERIALPARSER_NONE; return true; } return false; } // TODO return gps->encode(in); if (gps) gps->encode(in); // NOT a new line if ( (in != '\n') && (in != '\r') ) { // Process 1 field at a time (when comma) if ( (processMode == WII5SERIALPARSER_RECORD) && (in == ',') ) { processBufferField(&buffer[fieldLoc], bufCount - fieldLoc); buffer[bufCount] = ','; bufCount++; fieldLoc = bufCount; // Next field will start here (could be null) } // Keep whole record else { if ((bufCount + 1) < bufMax) { buffer[bufCount] = in; bufCount++; buffer[bufCount] = '\0'; } else { // TODO Log - just once, FATAL // last = WII5SPARTONRETURN_ERROR; } } } // New line - end of record else { writePassthrough(); writeCapture(); // CSV Data if (processMode == WII5SERIALPARSER_RECORD) { processBufferField(&buffer[fieldLoc], bufCount - fieldLoc); processRecord(); } else if (processMode == WII5SERIALPARSER_OKHUH) { processBufferOK(); } // An unknown line else { processBufferLine(); } bufCount = 0; // buffer[0] = '\0'; fieldCount = 0; fieldLoc = 0; return true; } return false; } void WII5SerialManager::writePassthrough() { if (passthrough && bufCount > 0) consoleBuffer(true); // Input } // Allow override of output of lines to log file (or even multiple) void WII5SerialManager::writeCapture() { } void WII5SerialManager::sendLine(uint16_t l) { programLine(l); stream->print(buffer); sendNewLine(); if (passthrough) consoleBuffer(); } // TODO: Move from buffer to wii5StringBuffer void WII5SerialManager::programLine(uint16_t l) { if (debug) console.log(LOG_DEBUG, F("SerialManager: set command=%d buffer=%s"), l, buffer); } void WII5SerialManager::consolePrefix(bool input) { if (input) console.printf(F("# Serial:< ")); else console.printf(F("# Serial:> ")); } void WII5SerialManager::consoleBuffer(bool input, bool useLog) { // TODO: This will fail if split comma and buffer gets added nulls if (useLog) { // TODO prefix? // TODO Log level? console.log(LOG_INFO, F("Serial:%s %s"), input ? "<" : ">", buffer); } else { consolePrefix(input); console.printf(buffer); console.printNewLine(); } } WII5_DRIVERS WII5SerialManager::driverId() { return WII5DRIVER_UNKNOWN; } /* void WII5SerialManager::displaySTATS() { // TODO wii5Display.atStatsSend console.printf(F("STATS:%s:SerialManager:TODO\r\n"), wii5Strings.strDriver(driverId())); } */ // Where? and What? - assumes buffer now has the comma separated data correct void WII5SerialManager::sendAtDataLine() { // In other words - assume this serial port, instead of say.... Console // if (!s) s = stream; - currently assumes console // NOTE: Steal the console buffer - so don't use it for debugging half way through here. // You can still use console.print, just not printf, or log // TODO How to calculate CNC wii5Display.atDataSend( F("wii5direct"), // 8 or 64? // NOTE: Must not add the "@" - it isn't used in CRC calculation F("%s"), buffer ); }