Ino
Ino
// Display settings
#define STATUS_SECTION_HEIGHT 32 // Top half of 64-pixel display
#define RESPONSE_SECTION_HEIGHT 32 // Bottom half of display
#define MAX_RESPONSE_HISTORY 4 // Store up to 4 recent responses
#define CHARS_PER_LINE 21 // Maximum characters per line
#define MESSAGE_TIMEOUT 2000 // Each message displays for 2 seconds
// System configuration
SystemConfig sysConfig;
int currentPreambleCount = PREAMBLE_COUNT; // Dynamically adjusted preamble
int currentPttDelay = PTT_DELAY_MS; // Dynamically adjusted PTT delay
// Timing variables
unsigned long lastDisplayUpdate = 0;
unsigned long lastGPSCheck = 0;
unsigned long lastActionTime = 0; // For OLED idle screen
unsigned long lastResponseRotation = 0; // For message rotation timing
unsigned long lastModemActivity = 0; // For modem watchdog
unsigned long lastSystemCheck = 0; // For system health checks
unsigned long lastMemoryCheck = 0; // For memory monitoring
unsigned long lastGPSSave = 0; // For GPS data saving
// Status tracking
bool debugMode = false; // Enhanced debugging output
unsigned long packetCounter = 0; // Count packets processed
unsigned long modemErrorCounter = 0; // Count modem communication errors
unsigned short modemResetCount = 0; // Track modem resets for escalation
bool transmissionInProgress = false; // Flag for transmission in progress
bool setupSuccess = true; // Track if setup completed successfully
bool isMasterPolling = false; // Flag when master is polling us
bool isWaitingForMaster = false; // Flag when waiting for master commands
// Function declarations
void logMessage(const char* message, bool isError, byte priority = 128);
void addToResponseHistory(const char* response, byte priority = 128);
void updateStatusSection();
void updateResponseSection();
void printSystemStatus();
void printHelpMenu();
void resetWatchdog();
bool validatePacketChecksum(const char* packet);
void sendResponse(const char* packetData);
void processCommand(const char* command);
bool checkGPSHealth();
bool formatGPSResponse(char* buffer, int maxSize);
bool saveGPSDataToEEPROM();
bool loadGPSDataFromEEPROM();
byte calculateConfigCRC(SystemConfig* config);
byte calculateGPSCRC(GPSData* data);
void displayWrappedMessage(const char* message, int row);
void checkResponseDisplay();
// Initialize SoftModem
modem.begin();
lastModemActivity = millis();
return true;
}
// Save to EEPROM
for (int i = 0; i < sizeof(SystemConfig); i++) {
EEPROM.write(SYSTEM_CONFIG_ADDR + i, ((byte*)&sysConfig)[i]);
}
return true;
}
// Verify CRC
byte expectedCRC = calculateConfigCRC(&sysConfig);
if (sysConfig.configVersion != 1 || sysConfig.crc != expectedCRC) {
logMessage("Invalid config CRC - using defaults", true, 50);
return false;
}
// Apply settings
debugMode = sysConfig.debugMode;
currentPttDelay = sysConfig.pttDelayMs;
currentPreambleCount = sysConfig.preambleCount;
return true;
}
// Save to EEPROM
for (int i = 0; i < sizeof(GPSData); i++) {
EEPROM.write(EEPROM_GPS_ADDR + i, ((byte*)&gpsData)[i]);
}
if (debugMode) {
snprintf(strBuffer, sizeof(strBuffer), "Saved GPS: %f, %f",
gpsData.latitude, gpsData.longitude);
logMessage(strBuffer, false, 150);
}
return true;
}
// Verify CRC
byte expectedCRC = calculateGPSCRC(&gpsData);
if (gpsData.crc != expectedCRC) {
logMessage("Invalid GPS data CRC", true, 50);
return false;
}
if (gpsData.isValid) {
snprintf(strBuffer, sizeof(strBuffer), "Loaded GPS: %f, %f",
gpsData.latitude, gpsData.longitude);
logMessage(strBuffer, false, 150);
}
return gpsData.isValid;
}
/* ============================================================================
AFSK Radio Communication System - Slave Controller (Part 2 of 3)
Packet handling, communication functions, and GPS processing
============================================================================
*/
return crc;
}
transmissionInProgress = true;
digitalWrite(TX_LED, HIGH);
// Activate PTT
digitalWrite(PTT_RELAY_PIN, HIGH);
delay(currentPttDelay);
// Calculate checksum
byte chk = calculateChecksum(payload, strlen(payload));
char chkHex[3];
byteToHex(chk, chkHex);
// Release PTT
digitalWrite(PTT_RELAY_PIN, LOW);
transmissionInProgress = false;
digitalWrite(TX_LED, LOW);
digitalWrite(RX_LED, HIGH);
// Pre-clear any lingering data in buffer
while (modem.available()) modem.read();
if (modem.available()) {
char c = modem.read();
lastModemActivity = millis(); // Update activity timestamp
if (c == '<') {
packet[0] = '<';
packetLen = 1;
startFound = true;
} else if (startFound && packetLen < maxSize - 1) {
packet[packetLen++] = c;
packet[packetLen] = '\0'; // Always null-terminate
if (c == '>') {
digitalWrite(RX_LED, LOW);
return true; // Successfully received complete packet
}
}
digitalWrite(RX_LED, LOW);
if (formatGPSResponse(responseData, sizeof(responseData))) {
// Send GPS data
sendResponse(responseData);
} else {
// Send NOFIX response
snprintf(responseData, sizeof(responseData), "%s NOFIX", SLAVE_ID);
sendResponse(responseData);
}
} else if (debugMode) {
snprintf(strBuffer, sizeof(strBuffer), "Command not for us: %s", payload);
logMessage(strBuffer, false, 200);
}
}
while (Serial.available()) {
char c = Serial.read();
gpsData.latitude = gps.location.lat();
gpsData.longitude = gps.location.lng();
gpsData.isValid = 1;
// Save GPS data to EEPROM (but limit writes to once per minute)
if (millis() - lastGPSSave > 60000) {
saveGPSDataToEEPROM();
lastGPSSave = millis();
}
/* ============================================================================
AFSK Radio Communication System - Slave Controller (Part 3 of 3)
Display functions, system status, and main program logic
============================================================================
*/
responseHistory[responseHistoryHead].timestamp = millis();
responseHistory[responseHistoryHead].isLongMessage = (strlen(response) >
CHARS_PER_LINE);
responseHistory[responseHistoryHead].priority = priority;
oled.setCursor(0, 3);
oled.print(F("LON: "));
oled.print(gps.location.lng(), 6);
oled.setCursor(0, 3);
oled.print(F("LON(mem): "));
oled.print(gpsData.longitude, 6);
}
// No GPS data at all
else {
oled.setCursor(0, 2);
oled.print(F("GPS: NO FIX"));
lastDisplayUpdate = millis();
}
if (gps.location.isValid()) {
Serial.print(F("GPS Position: "));
Serial.print(gps.location.lat(), 6);
Serial.print(F(", "));
Serial.println(gps.location.lng(), 6);
Serial.print(F("GPS Date/Time: "));
if (gps.date.isValid() && gps.time.isValid()) {
char dateTime[22];
sprintf(dateTime, "%04d-%02d-%02d %02d:%02d:%02d",
gps.date.year(), gps.date.month(), gps.date.day(),
gps.time.hour(), gps.time.minute(), gps.time.second());
Serial.println(dateTime);
} else {
Serial.println(F("Invalid"));
}
} else if (gpsData.isValid) {
Serial.println(F("Using stored GPS data:"));
Serial.print(F("GPS Position: "));
Serial.print(gpsData.latitude, 6);
Serial.print(F(", "));
Serial.println(gpsData.longitude, 6);
}
Serial.println(F("=======================\n"));
}
Serial.println(F("\nTest Commands:"));
Serial.print(F(" <"));
Serial.print(SLAVE_ID);
Serial.println(F("> - Simulate master poll"));
Serial.println(F(" <CFG:PTTDELAY=500> - Set PTT delay (NOT IMPLEMENTED)"));
Serial.println(F("====================\n"));
}
if (modem.available()) {
char command[MAX_PACKET_SIZE];
if (receiveSoftPacket(command, sizeof(command), 1000)) {
// Process the command
processCommand(command);
// Log it
addToResponseHistory(command, 50);
}
}
}
}
// Initialize SoftModem
if (!setupModem()) {
Serial.println(F("WARNING: SoftModem initialization failed"));
setupSuccess = false;
}
// End of code