#include "cli.h" void Cli::resetNextWord () { _currentWordIndex = -1; } const char* Cli::findNextWord () { if (_currentWordIndex >= 0) // skip current word while (_currentWordIndex < (int)_currentInput.length()) { if (_currentInput[_currentWordIndex] == 32) break; _currentWordIndex++; } else _currentWordIndex = 0; // skip spaces while (_currentWordIndex < (int)_currentInput.length()) { if (_currentInput[_currentWordIndex] != 32) break; _currentWordIndex++; } const char* ret = _currentWordIndex < (int)_currentInput.length()? _currentInput.c_str() + _currentWordIndex: nullptr; for (_currentWordEndIndex = _currentWordIndex; _currentInput.c_str()[_currentWordEndIndex] > 32; _currentWordEndIndex++); //if (ret) // Serial.printf("# found new start of word '%s'\n", ret); return ret; } void Cli::copyNextToTemp () { findNextWord(); int start = _currentWordIndex; int end = _currentWordEndIndex; if (start < end) _temp = _currentInput.substring(start, end); else _temp.clear(); } bool Cli::kw (const __FlashStringHelper* keyWord, const char* input) { // return true if input matches keyWord auto len = strlen_P((PGM_P)keyWord); return len && strncasecmp_P((PGM_P)keyWord, input, len) == 0; } bool Cli::kw (const __FlashStringHelper* keyWord) { return kw(keyWord, _currentInput.c_str() + _currentWordIndex); } void Cli::syntax (const __FlashStringHelper* cmd) { syntax((PGM_P)cmd); } void Cli::syntax (const char* cmd) { if (!cmd || kw(F("AT"), cmd)) Serial.printf("%sAT -> OK\n", _msgHeader.c_str()); if (!cmd || kw(F("HELP"), cmd)) Serial.printf("%sHELP [CMD]\n", _msgHeader.c_str()); if (!cmd || kw(F("RAT"), cmd)) Serial.printf("%sRAT C <rate> <unit> - set rate (UM/MM/UH/MH)\n", _msgHeader.c_str()); if (!cmd || kw(F("VOL"), cmd)) Serial.printf("%sVOL [ <vol> | <unit> ] - set volume or unit (UL / ML)\n", _msgHeader.c_str()); if (!cmd || kw(F("DIA"), cmd)) Serial.printf("%sDIA [ <mm> ] - set or show inside syringe diameter (mm)\n", _msgHeader.c_str()); if (!cmd || kw(F("DIR"), cmd)) Serial.printf("%sDIR [ INF | WDR ] - set or show direction (INFuse / WithDRaw)\n", _msgHeader.c_str()); if (!cmd || kw(F("FIL"), cmd)) Serial.printf("%sFIL - start filling using direction at rate for volume\n", _msgHeader.c_str()); if (!cmd || kw(F("STP"), cmd)) Serial.printf("%sSTP - stop\n", _msgHeader.c_str()); } void Cli::answer (bool ok, const String& error_message) const { Serial.printf("%s%s", _msgHeader.c_str(), ok? "OK": "ERROR"); if (!ok && error_message.length()) Serial.printf(" (%s)", error_message.c_str()); Serial.printf("\n"); } void Cli::loop (Stream& input) { while (true) { if (!input.available()) return; int c = input.read(); if (c == 13) break; if (c >= 32 && c <= 127) _currentInput += (char)c; // ignore other chars } resetNextWord(); Syringe::Syringe_configuration_t conf = syringe.configuration(); if (findNextWord()) { if (kw(F("AT"))) { answer(true); } else if (kw(F("HELP"))) { copyNextToTemp(); if (_temp.length()) syntax(_temp.c_str()); else syntax(); } else if (kw(F("RAT"))) { copyNextToTemp(); if (_temp.equalsIgnoreCase(F("C"))) { copyNextToTemp(); conf.rate_ul_per_s = atof(_temp.c_str()); copyNextToTemp(); if (_temp.equalsIgnoreCase(F("us"))) conf.rate_ul_per_s *= 1; else if (_temp.equalsIgnoreCase(F("ms"))) conf.rate_ul_per_s *= 1000; else if (_temp.equalsIgnoreCase(F("um"))) conf.rate_ul_per_s *= 60; else if (_temp.equalsIgnoreCase(F("mm"))) conf.rate_ul_per_s *= 60 * 1000; else if (_temp.equalsIgnoreCase(F("uh"))) conf.rate_ul_per_s *= 3600; else if (_temp.equalsIgnoreCase(F("mh"))) conf.rate_ul_per_s *= 3600 * 1000; else conf.rate_ul_per_s = -1; answer(syringe.configureSyringe(conf), F("invalid rate")); } else answer(false, F("RAT must be followed by C")); Serial.printf("%sRAT: %g ul/s - %g ul/mn - %g ul/h\n", _msgHeader.c_str(), syringe.configuration().rate_ul_per_s, syringe.configuration().rate_ul_per_s / 60, syringe.configuration().rate_ul_per_s / 3600); } else if (kw(F("VOL"))) { copyNextToTemp(); if (_temp.length()) { if (isdigit(_temp[0])) conf.volume_value = atof(_temp.c_str()); else if (_temp.equalsIgnoreCase(F("ul"))) conf.volume_unit_ul = 1; else if (_temp.equalsIgnoreCase(F("ml"))) conf.volume_unit_ul = 1000; else if (_temp.equalsIgnoreCase(F("cl"))) conf.volume_unit_ul = 10000; else if (_temp.equalsIgnoreCase(F("dl"))) conf.volume_unit_ul = 100000; else if (_temp.equalsIgnoreCase(F("l"))) conf.volume_unit_ul = 1000000; else conf.volume_unit_ul = 0; answer(syringe.configureSyringe(conf), F("invalid volume or volume unit")); } Serial.printf("%sVOL: %g ul\n", _msgHeader.c_str(), syringe.configuration().volume_value * syringe.configuration().volume_unit_ul); } else if (kw(F("DIA"))) { copyNextToTemp(); if (_temp.length()) { conf.diameter_mm = atof(_temp.c_str()); answer(syringe.configureSyringe(conf), F("invalid diameter")); } Serial.printf("%sDIA: %g mm\n", _msgHeader.c_str(), syringe.configuration().diameter_mm); } else if (kw(F("DIR"))) { copyNextToTemp(); if (_temp.length()) { if (_temp.equalsIgnoreCase(F("inf"))) conf.direction = 1; else if (_temp.equalsIgnoreCase(F("wdr"))) conf.direction = -1; else conf.direction = 0; answer(syringe.configureSyringe(conf), F("invalid direction -> INF | WDR")); } Serial.printf("%sDIR: %s\n", _msgHeader.c_str(), syringe.configuration().direction > 0? "INFuse": "WithDRaw"); } else if (kw(F("FIL"))) { syringe.fill(); answer(true); } else if (kw(F("STP"))) { syringe.setEmergency(); syringe.stayHere(); syringe.setEmergency(false); answer(true); } else { Serial.printf("%sinvalid command '%s'\n", _msgHeader.c_str(), _currentInput.c_str() + _currentWordIndex); } } if (findNextWord()) Serial.printf("%sgarbage at end of line: '%s'\n", _msgHeader.c_str(), _currentInput.c_str() + _currentWordIndex); resetNextWord(); _currentInput.clear(); }