Skip to content
Snippets Groups Projects
cli.cpp 9.88 KiB
Newer Older

#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;
David Gauchard's avatar
David Gauchard committed
    
    for (_currentWordEndIndex = _currentWordIndex; _currentInput.c_str()[_currentWordEndIndex] > 32; _currentWordEndIndex++);
    
David Gauchard's avatar
David Gauchard committed
#if 0
    if (ret)
        Serial.printf("# found new start of word '%s'\n", ret);
    else
        Serial.printf("# no more word?\n");
#endif
    return ret;
}

void Cli::copyNextToTemp ()
{
David Gauchard's avatar
David Gauchard committed
    if (!findNextWord())
    {
        _temp = emptyString;
        return;
    }
    int start = _currentWordIndex;
David Gauchard's avatar
David Gauchard committed
    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)
{
David Gauchard's avatar
David Gauchard committed
    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());
David Gauchard's avatar
David Gauchard committed
    if (!cmd || kw(F("RAT"), cmd))  Serial.printf("%sRAT [ C <rate> <unit> ] - set or show 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("MM"),  cmd))  Serial.printf("%sMM  [ <len> ]           - set distance (mm)\n", _msgHeader.c_str());
David Gauchard's avatar
David Gauchard committed
    if (!cmd || kw(F("DIA"), cmd))  Serial.printf("%sDIA [ <dia> ]           - set or show inside syringe diameter (in 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());
    if (!cmd || kw(F("DIS"), cmd))  Serial.printf("%sDIS                     - show volume dispensed\n", _msgHeader.c_str());
    if (!cmd || kw(F("ZERO"), cmd)) Serial.printf("%sZERO                    - go to stopper\n", _msgHeader.c_str());
    if (!cmd || kw(F("SET0"), cmd)) Serial.printf("%sSET0                    - change ZERO to current position\n", _msgHeader.c_str());
    if (!cmd || kw(F("NLCK"), cmd)) Serial.printf("%sNLCK                    - try to get out from stopper\n", _msgHeader.c_str());
    if (!cmd || kw(F("CONF"), cmd)) Serial.printf("%sCONF                    - show configuration\n", _msgHeader.c_str());
David Gauchard's avatar
David Gauchard committed
}

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::checkEmergency ()
{
    if (syringe.emergency())
        Serial.printf("%s: in EMERGENCY state, try NLCK command\n", _msgHeader.c_str());
}

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;
David Gauchard's avatar
David Gauchard committed
        // ignore other chars
David Gauchard's avatar
David Gauchard committed
    Syringe::Syringe_configuration_t conf = syringe.configuration();

    if (findNextWord())
    {
        if (kw(F("AT")))
        {
David Gauchard's avatar
David Gauchard committed
            answer(true);
        }
        else if (kw(F("HELP")))
        {
            copyNextToTemp();
            if (_temp.length())
                syntax(_temp.c_str());
            else
David Gauchard's avatar
David Gauchard committed
                syntax();
        }
        else if (kw(F("RAT")))
        {
            copyNextToTemp();
David Gauchard's avatar
David Gauchard committed
            if (_temp.equalsIgnoreCase(F("C")))
            {
                copyNextToTemp();
                conf.rate_ul_per_s = atof(_temp.c_str());
                copyNextToTemp();
David Gauchard's avatar
David Gauchard committed
                if (_temp.length() == 0)
                    conf.rate_ul_per_s *= 60; // default uL/mn
                else if (_temp.equalsIgnoreCase(F("us")))
David Gauchard's avatar
David Gauchard committed
                    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")))
David Gauchard's avatar
David Gauchard committed
                    conf.rate_ul_per_s *= 1000 * 60.0;
David Gauchard's avatar
David Gauchard committed
                else if (_temp.equalsIgnoreCase(F("uh")))
                    conf.rate_ul_per_s *= 3600;
                else if (_temp.equalsIgnoreCase(F("mh")))
David Gauchard's avatar
David Gauchard committed
                    conf.rate_ul_per_s *= 1000 * 3600.0;
David Gauchard's avatar
David Gauchard committed
                else
                    conf.rate_ul_per_s = -1;
David Gauchard's avatar
David Gauchard committed
                answer(syringe.configureSyringe(conf), F("invalid 'RAT C rate [unit]'"));
David Gauchard's avatar
David Gauchard committed
            else if (_temp.length() > 0)
David Gauchard's avatar
David Gauchard committed
                answer(false, F("RAT must be followed by C"));

David Gauchard's avatar
David Gauchard committed
            Serial.printf("%sRAT: %g ul/mn\n", _msgHeader.c_str(),
                syringe.configuration().rate_ul_per_s / 60);
David Gauchard's avatar
David Gauchard committed
            copyNextToTemp();
David Gauchard's avatar
David Gauchard committed
            while (_temp.length())
David Gauchard's avatar
David Gauchard committed
            {
                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;
                answer(syringe.configureSyringe(conf), F("invalid volume or volume unit"));
David Gauchard's avatar
David Gauchard committed
                copyNextToTemp();
            Serial.printf("%sVOL: %g ul (%g mm)\n", _msgHeader.c_str(), syringe.confToMm3(), syringe.confToMm());
        }
        else if (kw(F("MM")))
        {
            copyNextToTemp();
            if (_temp.length())
            {
                if (isdigit(_temp[0]))
                    conf.volume_value = syringe.mmToMm3(atof(_temp.c_str()));
                answer(syringe.configureSyringe(conf), F("invalid length"));
                copyNextToTemp();
            }

            Serial.printf("%sVOL: %g ul (%g mm)\n", _msgHeader.c_str(), syringe.confToMm3(), syringe.confToMm());
David Gauchard's avatar
David Gauchard committed
        }
        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")))
        {
            checkEmergency();
David Gauchard's avatar
David Gauchard committed
            syringe.fill();
            answer(true);
        }
        else if (kw(F("STP")))
        {
            syringe.setEmergency();
            syringe.stayHere();
            syringe.setEmergency(false);
            answer(true);
        else if (kw(F("SET0")))
        {
            syringe.resetPosition();
            answer(true);
        }
David Gauchard's avatar
David Gauchard committed
        else if (kw(F("DIS")))
        {
            Serial.printf("%sDIS: I %g W %g UL\n", _msgHeader.c_str(),
                conf.direction > 0? syringe.mmToMm3(syringe.stepToMm(syringe.whereStep())): 0,
                conf.direction > 0? 0 : syringe.mmToMm3(syringe.stepToMm(syringe.whereStep())));
        }
        else if (kw(F("ZERO")))
        {
            checkEmergency();
            syringe.gotoZero();
            answer(true);
        }
        else if (kw(F("NLCK")))
        {
            syringe.leaveEmergency();
            answer(true);
        }
        else if (kw(F("CONF")))
        {
            syringe.showConfiguration(syringe.configuration());
            answer(true);
        }
David Gauchard's avatar
David Gauchard committed
            Serial.printf("%sinvalid command '%s'\n", _msgHeader.c_str(), _currentInput.c_str() + _currentWordIndex);
David Gauchard's avatar
David Gauchard committed
        Serial.printf("%sgarbage at end of line: '%s'\n", _msgHeader.c_str(), _currentInput.c_str() + _currentWordIndex);
    resetNextWord();
    _currentInput.clear();
}