#include "board.h" #include "mw.h" // we unset this on 'exit' extern uint8_t cliMode; static void cliDefaults(char *cmdline); static void cliExit(char *cmdline); static void cliFeature(char *cmdline); static void cliHelp(char *cmdline); static void cliMap(char *cmdline); static void cliMixer(char *cmdline); static void cliSave(char *cmdline); static void cliSet(char *cmdline); static void cliStatus(char *cmdline); static void cliVersion(char *cmdline); // from sensors.c extern uint8_t batteryCellCount; // from config.c RC Channel mapping extern const char rcChannelLetters[]; // buffer static char cliBuffer[32]; static uint8_t bufferIndex = 0; // sync this with MultiType enum from mw.h const char *mixerNames[] = { "TRI", "QUADP", "QUADX", "BI", "GIMBAL", "Y6", "HEX6", "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX", "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4", NULL }; // sync this with AvailableFeatures enum from board.h const char *featureNames[] = { "PPM", "VBAT", "INFLIGHT_ACC_CAL", "SPEKTRUM", "MOTOR_STOP", "SERVO_TILT", "CAMTRIG", "GYRO_SMOOTHING", "LED_RING", "GPS", NULL }; // sync this with AvailableSensors enum from board.h const char *sensorNames[] = { "ACC", "BARO", "MAG", "SONAR", "GPS", NULL }; typedef struct { char *name; char *param; void (*func)(char *cmdline); } clicmd_t; // should be sorted a..z for bsearch() const clicmd_t cmdTable[] = { { "defaults", "reset to defaults and reboot", cliDefaults }, { "exit", "", cliExit }, { "feature", "list or -val or val", cliFeature }, { "help", "", cliHelp }, { "map", "mapping of rc channel order", cliMap }, { "mixer", "mixer name or list", cliMixer }, { "save", "save and reboot", cliSave }, { "set", "name=value or blank for list", cliSet }, { "status", "show system status", cliStatus }, { "version", "", cliVersion }, }; #define CMD_COUNT (sizeof(cmdTable) / sizeof(cmdTable[0])) typedef enum { VAR_UINT8, VAR_INT8, VAR_UINT16, VAR_INT16, VAR_UINT32 } vartype_e; typedef struct { const char *name; const uint8_t type; // vartype_e void *ptr; const int32_t min; const int32_t max; } clivalue_t; const clivalue_t valueTable[] = { { "deadband", VAR_UINT8, &cfg.deadband, 0, 32 }, { "yawdeadband", VAR_UINT8, &cfg.yawdeadband, 0, 100 }, { "midrc", VAR_UINT16, &cfg.midrc, 1200, 1700 }, { "minthrottle", VAR_UINT16, &cfg.minthrottle, 0, 2000 }, { "maxthrottle", VAR_UINT16, &cfg.maxthrottle, 0, 2000 }, { "mincommand", VAR_UINT16, &cfg.mincommand, 0, 2000 }, { "mincheck", VAR_UINT16, &cfg.mincheck, 0, 2000 }, { "maxcheck", VAR_UINT16, &cfg.maxcheck, 0, 2000 }, { "motor_pwm_rate", VAR_UINT16, &cfg.motor_pwm_rate, 50, 498 }, { "servo_pwm_rate", VAR_UINT16, &cfg.servo_pwm_rate, 50, 498 }, { "spektrum_hires", VAR_UINT8, &cfg.spektrum_hires, 0, 1 }, { "vbatscale", VAR_UINT8, &cfg.vbatscale, 10, 200 }, { "vbatmaxcellvoltage", VAR_UINT8, &cfg.vbatmaxcellvoltage, 10, 50 }, { "vbatmincellvoltage", VAR_UINT8, &cfg.vbatmincellvoltage, 10, 50 }, { "yaw_direction", VAR_INT8, &cfg.yaw_direction, -1, 1 }, { "wing_left_mid", VAR_UINT16, &cfg.wing_left_mid, 0, 2000 }, { "wing_right_mid", VAR_UINT16, &cfg.wing_right_mid, 0, 2000 }, { "tri_yaw_middle", VAR_UINT16, &cfg.tri_yaw_middle, 0, 2000 }, { "tri_yaw_min", VAR_UINT16, &cfg.tri_yaw_min, 0, 2000 }, { "tri_yaw_max", VAR_UINT16, &cfg.tri_yaw_max, 0, 2000 }, { "tilt_pitch_prop", VAR_INT8, &cfg.tilt_pitch_prop, -100, 100 }, { "tilt_roll_prop", VAR_INT8, &cfg.tilt_roll_prop, -100, 100 }, { "acc_lpf_factor", VAR_UINT8, &cfg.acc_lpf_factor, 0, 32 }, { "gyro_lpf", VAR_UINT16, &cfg.gyro_lpf, 0, 256 }, { "gps_baudrate", VAR_UINT32, &cfg.gps_baudrate, 1200, 115200 }, { "serial_baudrate", VAR_UINT32, &cfg.serial_baudrate, 1200, 115200 }, { "p_pitch", VAR_UINT8, &cfg.P8[PITCH], 0, 200}, { "i_pitch", VAR_UINT8, &cfg.I8[PITCH], 0, 200}, { "d_pitch", VAR_UINT8, &cfg.D8[PITCH], 0, 200}, { "p_roll", VAR_UINT8, &cfg.P8[ROLL], 0, 200}, { "i_roll", VAR_UINT8, &cfg.I8[ROLL], 0, 200}, { "d_roll", VAR_UINT8, &cfg.D8[ROLL], 0, 200}, { "p_yaw", VAR_UINT8, &cfg.P8[YAW], 0, 200}, { "i_yaw", VAR_UINT8, &cfg.I8[YAW], 0, 200}, { "d_yaw", VAR_UINT8, &cfg.D8[YAW], 0, 200}, { "p_level", VAR_UINT8, &cfg.P8[PIDLEVEL], 0, 200}, { "i_level", VAR_UINT8, &cfg.I8[PIDLEVEL], 0, 200}, { "d_level", VAR_UINT8, &cfg.D8[PIDLEVEL], 0, 200}, }; #define VALUE_COUNT (sizeof(valueTable) / sizeof(valueTable[0])) static void cliSetVar(const clivalue_t *var, const int32_t value); static void cliPrintVar(const clivalue_t *var); #ifndef HAVE_ITOA_FUNCTION /* ** The following two functions together make up an itoa() ** implementation. Function i2a() is a 'private' function ** called by the public itoa() function. ** ** itoa() takes three arguments: ** 1) the integer to be converted, ** 2) a pointer to a character conversion buffer, ** 3) the radix for the conversion ** which can range between 2 and 36 inclusive ** range errors on the radix default it to base10 ** Code from http://groups.google.com/group/comp.lang.c/msg/66552ef8b04fe1ab?pli=1 */ static char *i2a(unsigned i, char *a, unsigned r) { if (i / r > 0) a = i2a(i / r, a, r); *a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i % r]; return a + 1; } char *itoa(int i, char *a, int r) { if ((r < 2) || (r > 36)) r = 10; if (i < 0) { *a = '-'; *i2a(-(unsigned)i, a + 1, r) = 0; } else *i2a(i, a, r) = 0; return a; } #endif static void cliPrompt(void) { uartPrint("\r\n# "); } static int cliCompare(const void *a, const void *b) { const clicmd_t *ca = a, *cb = b; return strncasecmp(ca->name, cb->name, strlen(cb->name)); } static void cliDefaults(char *cmdline) { uartPrint("Resetting to defaults...\r\n"); checkFirstTime(true); uartPrint("Rebooting..."); delay(10); systemReset(false); } static void cliExit(char *cmdline) { uartPrint("\r\nLeaving CLI mode...\r\n"); memset(cliBuffer, 0, sizeof(cliBuffer)); bufferIndex = 0; cliMode = 0; // save and reboot... I think this makes the most sense cliSave(cmdline); } static void cliFeature(char *cmdline) { uint8_t i; uint8_t len; uint32_t mask; len = strlen(cmdline); mask = featureMask(); if (len == 0) { uartPrint("Enabled features: "); for (i = 0; ; i++) { if (featureNames[i] == NULL) break; if (mask & (1 << i)) uartPrint((char *)featureNames[i]); uartWrite(' '); } uartPrint("\r\n"); } else if (strncasecmp(cmdline, "list", len) == 0) { uartPrint("Available features: "); for (i = 0; ; i++) { if (featureNames[i] == NULL) break; uartPrint((char *)featureNames[i]); uartWrite(' '); } uartPrint("\r\n"); return; } else { bool remove = false; if (cmdline[0] == '-') { // remove feature remove = true; cmdline++; // skip over - len--; } for (i = 0; ; i++) { if (featureNames[i] == NULL) { uartPrint("Invalid feature name...\r\n"); break; } if (strncasecmp(cmdline, featureNames[i], len) == 0) { if (remove) { featureClear(1 << i); uartPrint("Disabled "); } else { featureSet(1 << i); uartPrint("Enabled "); } uartPrint((char *)featureNames[i]); uartPrint("\r\n"); break; } } } } static void cliHelp(char *cmdline) { uint8_t i = 0; uartPrint("Available commands:\r\n"); for (i = 0; i < CMD_COUNT; i++) { uartPrint(cmdTable[i].name); uartWrite('\t'); uartPrint(cmdTable[i].param); uartPrint("\r\n"); while (!uartTransmitEmpty()); } } static void cliMap(char *cmdline) { uint8_t len; uint8_t i; char out[9]; len = strlen(cmdline); if (len == 8) { // uppercase it for (i = 0; i < 8; i++) cmdline[i] = toupper(cmdline[i]); for (i = 0; i < 8; i++) { if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i])) continue; uartPrint("Must be any order of AETR1234\r\n"); return; } parseRcChannels(cmdline); } uartPrint("Current assignment: "); for (i = 0; i < 8; i++) out[cfg.rcmap[i]] = rcChannelLetters[i]; out[i] = '\0'; uartPrint(out); uartPrint("\r\n"); } static void cliMixer(char *cmdline) { uint8_t i; uint8_t len; len = strlen(cmdline); if (len == 0) { uartPrint("Current mixer: "); uartPrint((char *)mixerNames[cfg.mixerConfiguration - 1]); uartPrint("\r\n"); return; } else if (strncasecmp(cmdline, "list", len) == 0) { uartPrint("Available mixers: "); for (i = 0; ; i++) { if (mixerNames[i] == NULL) break; uartPrint((char *)mixerNames[i]); uartWrite(' '); } uartPrint("\r\n"); return; } for (i = 0; ; i++) { if (mixerNames[i] == NULL) { uartPrint("Invalid mixer type...\r\n"); break; } if (strncasecmp(cmdline, mixerNames[i], len) == 0) { cfg.mixerConfiguration = i + 1; uartPrint("Mixer set to "); uartPrint((char *)mixerNames[i]); uartPrint("\r\n"); break; } } } static void cliSave(char *cmdline) { uartPrint("Saving..."); writeParams(); uartPrint("\r\nRebooting..."); delay(10); systemReset(false); } static void cliPrintVar(const clivalue_t *var) { int32_t value = 0; char buf[16]; switch (var->type) { case VAR_UINT8: value = *(uint8_t *)var->ptr; break; case VAR_INT8: value = *(int8_t *)var->ptr; break; case VAR_UINT16: value = *(uint16_t *)var->ptr; break; case VAR_INT16: value = *(int16_t *)var->ptr; break; case VAR_UINT32: value = *(uint32_t *)var->ptr; break; } itoa(value, buf, 10); uartPrint(buf); } static void cliSetVar(const clivalue_t *var, const int32_t value) { switch (var->type) { case VAR_UINT8: case VAR_INT8: *(char *)var->ptr = (char)value; break; case VAR_UINT16: case VAR_INT16: *(short *)var->ptr = (short)value; break; case VAR_UINT32: *(int *)var->ptr = (int)value; break; } } static void cliSet(char *cmdline) { uint8_t i; uint8_t len; const clivalue_t *val; char *eqptr = NULL; int32_t value = 0; len = strlen(cmdline); if (len == 0) { uartPrint("Current settings: \r\n"); for (i = 0; i < VALUE_COUNT; i++) { val = &valueTable[i]; uartPrint((char *)valueTable[i].name); uartPrint(" = "); cliPrintVar(val); uartPrint("\r\n"); while (!uartTransmitEmpty()); } } else if ((eqptr = strstr(cmdline, "="))) { // has equal, set var eqptr++; len--; value = atoi(eqptr); for (i = 0; i < VALUE_COUNT; i++) { val = &valueTable[i]; if (strncasecmp(cmdline, valueTable[i].name, strlen(valueTable[i].name)) == 0) { // found if (value >= valueTable[i].min && value <= valueTable[i].max) { cliSetVar(val, value); uartPrint((char *)valueTable[i].name); uartPrint(" set to "); cliPrintVar(val); } else { uartPrint("ERR: Value assignment out of range\r\n"); } return; } } uartPrint("ERR: Unknown variable name\r\n"); } } static void cliStatus(char *cmdline) { char buf[16]; uint8_t i; uint32_t mask; uartPrint("System Uptime: "); itoa(millis() / 1000, buf, 10); uartPrint(buf); uartPrint(" seconds, Voltage: "); itoa(vbat, buf, 10); uartPrint(buf); uartPrint(" * 0.1V ("); itoa(batteryCellCount, buf, 10); uartPrint(buf); uartPrint("S battery)\r\n"); mask = sensorsMask(); uartPrint("Detected sensors: "); for (i = 0; ; i++) { if (sensorNames[i] == NULL) break; if (mask & (1 << i)) uartPrint((char *)sensorNames[i]); uartWrite(' '); } uartPrint("\r\n"); uartPrint("Cycle Time: "); itoa(cycleTime, buf, 10); uartPrint(buf); uartPrint(", I2C Errors: "); itoa(i2cGetErrorCounter(), buf, 10); uartPrint(buf); uartPrint("\r\n"); } static void cliVersion(char *cmdline) { uartPrint("Afro32 CLI version 2.0-pre3 " __DATE__ " / " __TIME__); } void cliProcess(void) { if (!cliMode) { cliMode = 1; uartPrint("\r\nEntering CLI Mode, type 'exit' to return, or 'help'\r\n"); cliPrompt(); } while (uartAvailable()) { uint8_t c = uartRead(); if (c == '\t' || c == '?') { // do tab completion const clicmd_t *cmd, *pstart = NULL, *pend = NULL; int i = bufferIndex; for (cmd = cmdTable; cmd < cmdTable + CMD_COUNT; cmd++) { if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) continue; if (!pstart) pstart = cmd; pend = cmd; } if (pstart) { /* Buffer matches one or more commands */ for (; ; bufferIndex++) { if (pstart->name[bufferIndex] != pend->name[bufferIndex]) break; if (!pstart->name[bufferIndex]) { /* Unambiguous -- append a space */ cliBuffer[bufferIndex++] = ' '; break; } cliBuffer[bufferIndex] = pstart->name[bufferIndex]; } } if (!bufferIndex || pstart != pend) { /* Print list of ambiguous matches */ uartPrint("\r\033[K"); for (cmd = pstart; cmd <= pend; cmd++) { uartPrint(cmd->name); uartWrite('\t'); } cliPrompt(); i = 0; /* Redraw prompt */ } for (; i < bufferIndex; i++) uartWrite(cliBuffer[i]); } else if (!bufferIndex && c == 4) { cliExit(cliBuffer); return; } else if (c == 12) { // clear screen uartPrint("\033[2J\033[1;1H"); cliPrompt(); } else if (bufferIndex && (c == '\n' || c == '\r')) { // enter pressed clicmd_t *cmd = NULL; clicmd_t target; uartPrint("\r\n"); cliBuffer[bufferIndex] = 0; // null terminate target.name = cliBuffer; target.param = NULL; cmd = bsearch(&target, cmdTable, CMD_COUNT, sizeof cmdTable[0], cliCompare); if (cmd) cmd->func(cliBuffer + strlen(cmd->name) + 1); else uartPrint("ERR: Unknown command, try 'help'"); memset(cliBuffer, 0, sizeof(cliBuffer)); bufferIndex = 0; // 'exit' will reset this flag, so we don't need to print prompt again if (!cliMode) return; cliPrompt(); } else if (c == 127) { // backspace if (bufferIndex) { cliBuffer[--bufferIndex] = 0; uartPrint("\010 \010"); } } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) { if (!bufferIndex && c == 32) continue; cliBuffer[bufferIndex++] = c; uartWrite(c); } } }