Initial public release of WII5 Buoy firmware
Firmware for an autonomous wave-measurement buoy (ATmega2560-based WII5 v2 board). Reads wave motion from a Sparton AHRS-M1/M2 IMU, samples GPS and battery state, and reports back over Iridium SBD satellite telemetry. Originally developed 2012-2024. This is the first public release. Code, documentation, and field-tested operating modes (Capture, Sleep, Position, ManualTest, SelfTest, LowBattery) are licensed under Apache 2.0 — see LICENSE and NOTICE. See README.md for an overview and build instructions, CONTRIBUTING.md for how to contribute, and DEPLOYMENTS.md for the field-deployment log.
This commit is contained in:
@@ -0,0 +1,553 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> 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 <Arduino.h>
|
||||
#include <WII5SerialManager.h>
|
||||
#include <WII5.h>
|
||||
|
||||
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>,<MOMSN>,<MT status>,<MTMSN>,<MT length>,<MT queued>
|
||||
// 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:<MO status>,<MOMSN>,<MT status>,<MTMSN>,<MT length>,<MT queued>
|
||||
// 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
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user