Вычисление простых математических выражений на ESP32/ESP8266/Arduino

При конфигурировании датчиков возникает необходимость ввести поправочные коэффициенты после калибровки. В идеале иметь возможность в конфигурационном файле задать простую математическую формулу для корректировки значений получаемых с датчика. К сожалению, для Arduino мне не удалось найти нормальную библиотеку для парсинга (вычисления) математических выражений заданных в виде строки.

Ресурсы микроконтроллера надо беречь, поэтому использовать код для продвинутых анализаторов строковых математических функций не годится. Поэтому я написал очень простой математический решатель, который не поддерживает приоритет математических операций и скобки, но его более чем достаточно для ввода поправочных коэффициентов.

Небольшой пример использования.

  String expression = "%V%*1.1+0.5";
  float value = modbusCfg.Eval(expression, 10.5);
  Serial.println("Value of " + expression + ": " + String(value)); 
  expression = "1.1+2.2*%V%+0.5";
  value = modbusCfg.Eval(expression, 10.5);
  Serial.println("Value of " + expression + ": " + String(value)); 
  expression = "1+0.5";
  value = modbusCfg.Eval(expression, 10);
  Serial.println("Value of " + expression + ": " + String(value)); 

В исходном строковом выражнии %V% заменяется на значение полученное от датчика. Например, датчики нередко возвращают int и для приведения цифр к нормальной величине нужно разделить на 10. В этом случае выражение будет %V%/10. При этом даже цифровые термодатчики врут, поскольку дешевые термодатчики, похоже, никто не калибрует. Например, если термодатчик врет на 0,7 градуса, показывая завышенные значения, то выражение будет %V%/10 + 0.7.

Поскольку, нередко, достаточно умножить полученное значение на 0.1, например. В этом случае в выражении достаточно написать 0.1 — операция умножения выполняется по-умолчанию, если не указан %V%.

Этот блок кода используется в моей библиотеке ModbusConfig для конфигурирования датчиков различного типа.

void ModbusConfig::math_operation(math_op op, float &prev_val, String &current_val, float &value)
{
  if (prev_val != -16101975) //Check if the default value has changed
  {
    if (current_val.length() > 0)
    {
      float second = current_val.toFloat();
      switch (op)
      {
      case math_op::Mul:
        value = prev_val * second;
        break;
      case math_op::Add:
        value = prev_val + second;
        break;
      case math_op::Div:
        value = prev_val / second;
        break;
      case math_op::Sub:
        value = prev_val - second;
        break;
      case math_op::None:
        printf("There is no operation.");
      }
	  prev_val = value;
      current_val = "";
    }
  }
  else
  {
    if (current_val.length() > 0)
    {
      prev_val = current_val.toFloat();
      current_val = "";
    }
  }
}

float ModbusConfig::Eval(String expr, float value)
{
  String pattern = "%V%";
  int pos = expr.indexOf(pattern);
  if (pos == -1)
  {
    expr = "%V%*" + expr;  
  }
  expr.replace("%V%", String(value));
  String val = "";
  math_op last_op = math_op::None;
  float prev_val = -16101975;//Set the some default value to check if the value has changed 
  value = 0;
  for (int i = 0; i < expr.length(); i++)
  {
    switch (expr[i])
    {
    case '*':
      math_operation(last_op, prev_val, val, value);
      last_op = math_op::Mul;
      break;
    case '+':
      math_operation(last_op, prev_val, val, value);
      last_op = math_op::Add;
      break;
    case '/':
      math_operation(last_op, prev_val, val, value);
      last_op = math_op::Div;
      break;
    case '-':
      math_operation(last_op, prev_val, val, value);
      last_op = math_op::Sub;
      break;
    default:
      if (isdigit(expr[i]) || expr[i] == '.' || expr[i] == ',')
      {
        val += (expr[i] == ',') ? '.' : expr[i];
      }
      break;
    }
  }
  math_operation(last_op, prev_val, val, value);
  return value;
}
Spread the love
Запись опубликована в рубрике IT рецепты. Добавьте в закладки постоянную ссылку.