AT Command System via UART

Solving Approach

  • Read UART commands and process them , create a lookup table for commands and map functions to each cmd
  • Added UNIT testing to test process commands

 

 

Code

/*Paste your code here*/

#include <Arduino.h>
#include <string.h>

#define UNIT_TEST   // Uncomment ONLY for unit testing

#ifdef UNIT_TEST
#include <ArduinoUnit.h>
#endif


#define LED_PIN 7
#define ADC_PIN A0
#define VCC 5.0

#define RX_BUFFER_SIZE 64

char rxBuffer[RX_BUFFER_SIZE];
char txBuffer[64];
uint8_t rxIndex = 0;

unsigned long startMillis;
bool ledState = false;

/* ===== Function Prototypes ===== */
bool processCommand(const char *cmd);
void sendSysOnTime(void);
void sendAdcValue(void);
void sendAdcVoltage(void);

/* ===== Command Lookup Table ===== */
typedef struct {
  const char *cmd;
  void (*handler)(void);
} AT_Command;

/* ===== Command Handlers ===== */
void cmdAT() {
  strcpy(txBuffer, "OK");
}

void cmdLedOn() {
#ifndef UNIT_TEST
  digitalWrite(LED_PIN, HIGH);
#endif
  ledState = true;
  strcpy(txBuffer, "+LED:\"ON\"");
}

void cmdLedOff() {
#ifndef UNIT_TEST
  digitalWrite(LED_PIN, LOW);
#endif
  ledState = false;
  strcpy(txBuffer, "+LED:\"OFF\"");
}

void cmdLedStatus() {
  strcpy(txBuffer, ledState ? "+LED_STATUS:\"ON\"" : "+LED_STATUS:\"OFF\"");
}

void cmdSysOnTime() {
  sendSysOnTime();
}

void cmdAdcValue() {
  sendAdcValue();
}

void cmdAdcVoltage() {
  sendAdcVoltage();
}

/* ===== Lookup Table ===== */
AT_Command commandTable[] = {
  { "AT", cmdAT },
  { "AT+LED=ON", cmdLedOn },
  { "AT+LED=OFF", cmdLedOff },
  { "AT+LED_STATUS?", cmdLedStatus },
  { "AT+SYS_ON_TIME?", cmdSysOnTime },
  { "AT+ADC_VALUE?", cmdAdcValue },
  { "AT+ADC_VOLTAGE?", cmdAdcVoltage }
};

const uint8_t commandCount = sizeof(commandTable) / sizeof(commandTable[0]);

/* ===== Setup ===== */
void setup() {
  Serial.begin(9600);
#ifndef UNIT_TEST
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
#endif
  startMillis = millis();
}

/* ===== UART Task (Production Only) ===== */
void uartTask(void) {
  while (Serial.available()) {
    char ch = Serial.read();

    if (ch == '\n' || ch == '\r') {
      if (rxIndex > 0) {
        rxBuffer[rxIndex] = '\0';
        processCommand(rxBuffer);
        Serial.println(txBuffer);
        rxIndex = 0;
      }
    } else if (rxIndex < RX_BUFFER_SIZE - 1) {
      rxBuffer[rxIndex++] = ch;
    }
  }
}

/* ===== Loop ===== */
void loop() {
#ifndef UNIT_TEST
  uartTask();
#else
  Test::run();
#endif
}

/* ===== Command Processor ===== */
bool processCommand(const char *cmd) {
  for (uint8_t i = 0; i < commandCount; i++) {
    if (strcmp(cmd, commandTable[i].cmd) == 0) {
      commandTable[i].handler();
      return true;
    }
  }
  strcpy(txBuffer, "ERROR");
  return false;
}

/* ===== Utility Functions ===== */
void sendSysOnTime() {
  unsigned long seconds = (millis() - startMillis) / 1000;
  uint8_t hh = seconds / 3600;
  uint8_t mm = (seconds % 3600) / 60;
  uint8_t ss = seconds % 60;

  sprintf(txBuffer, "+SYS_ON_TIME:\"%02u:%02u:%02u\"", hh, mm, ss);
}

void sendAdcValue() {
#ifdef UNIT_TEST
  int value = 512;  // mocked
#else
  int value = analogRead(ADC_PIN);
#endif
  sprintf(txBuffer, "+ADC_VALUE:%d", value);
}

void sendAdcVoltage() {
#ifdef UNIT_TEST
  float voltage = 2.50;  // mocked
#else
  int adc = analogRead(ADC_PIN);
  float voltage = (adc * VCC) / 1023.0;
#endif
  sprintf(txBuffer, "+ADC_VOLTAGE:%.3f", voltage);
}

#ifdef UNIT_TEST

test(AT_OK) {
  processCommand("AT");
  assertEqual(txBuffer, "OK");
}

test(LED_ON) {
  processCommand("AT+LED=ON");
  assertTrue(ledState);
}

test(INVALID_CMD) {
  processCommand("XYZ");
  assertEqual(txBuffer, "ERROR");
}

#endif

 

Output

Video

Add a video of the output (know more)

 

 

 

 

 

Photo of Output

Add a photo of your hardware showing the output.

 

 

 

 

Screenshot of Serial Terminal 

Add a Screenshot of the serial terminal showing the output.


 

Upvote
Downvote

Submit Your Solution

Note: Once submitted, your solution goes public, helping others learn from your approach!