Парсинг текстового файла от датчиков температуры

KotBegemot
Offline
Зарегистрирован: 15.05.2020

Добрый день.

Просьба помочь.
Показания датчиков температуры пишутся с MEGA2560 в data.txt по времени(часы:минуты), каждые 10 мин. По каждому датчику - две температуры: set и act. Сколько датчиков будет - заранее неизвестно, от 1 до 16, например. Затем идет запрос от юзера на показания конкретного датчика (по id) за заданный период времени - от (часы:минуты) до (часы:минуты).

Нужно выбрать из файла нужные строки, отвечающие заданному промежутку времени, и из каждой строки вытянуть set и act по запрашиваемому id.

Строку представляю себе такую: "18:22;id=1:14.50:28.20;id=2:23.60:29.10;...id=n:16.00:22.00;\n".
Могу написать другую.

Результат нужен в виде массивов hour[], minute[], set[], act[]. 

Читал про функцию strtok, но не могу сформировать правильный код.
Благодарен за любую помощь.

b707
Онлайн
Зарегистрирован: 26.05.2017

выделите время специальными маркерами, например #18:22# - будет проще искать

остальное можно оставить как у вас.

Далее просто - находите время, далее читаете данные до символа точка с запятой ';' - это будет показание одного датчика. Разбиваете на поля по символу ':'. извлекаете id.сравниваете с нужным, если надо сохраняете поля в массивы

KotBegemot
Offline
Зарегистрирован: 15.05.2020

Спасибо за быстрый ответ.

Логика мне понятна и не вызывает вопросов. Проблема - написать код. Хотя бы похожий пример нужен.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

KotBegemot пишет:
Хотя бы похожий пример нужен.
Вот похожий пример. Только с учётом поста №5 в том же топике, ниже.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

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

Решение по оптимизации, в принципе, лежит на поверхности: необязательно отказываться от текстового формата, достаточно постулировать, что все записи имеют одинаковую длину (пустое место забивать пробелами, например), а в начале записи лежит N символов метки времени.

Тогда по быстродействию всё сильно веселее в случае поиска диапазона (постулируем, что данные в логе идут по времени от меньшего к большему, т.е. сортированы): делим размер файла на 2, тыкаемся от этой позиции, находим начало записи (оно тоже должно быть формализовано, например, каким-нибудь набором символов). Нашли начало записи? Запомнили позицию этой записи в файле. Ну а далее, зная позицию средней записи в файле и длину записи, простым тупым делением пополам очень быстро находим начало диапазона. И от него уже вычитываем все строки до конца диапазона. Экономия по операциям чтения с файла - колоссальная: чтобы понять, подпадает ли строка под диапазон - достаточно вычитать N байт метки времени, вместо того, чтобы читать всё строку.

Быстрее - будет только бинарный формат. Но он - не human readable, от слова "совсем".

KotBegemot
Offline
Зарегистрирован: 15.05.2020

Евгений, я видел этот пример. Спасибо. Только не смог его доработать. Я из Java, мне тут перестраиваться сложно. Там насплитил и всё.
Делаю в стрингах пока, потом вышлю - может кто поможет оптимизировать под чары. А то на стринги все ругаются за память.

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

А что за клиент читает данные для построения выходных данных из лога? Куда именно этот файл данных data.txt пишется? Ну и в качестве "бреда" - имеется ли возможность все это передать на ПК?

KotBegemot
Offline
Зарегистрирован: 15.05.2020

Данные нужны для построения графика температур на ПК по запросу, не регулярно. Файл пишется на SD карту.

KotBegemot
Offline
Зарегистрирован: 15.05.2020

В общем получился у меня такой код. Проверил - работает.
Строка из файла имеет вид: "#7:18:22#id=1:14.50:28.20;id=2:23.60:29.10;...id=n:16.00:22.00;;". Первая "7" - это день недели.
Для извлечения подстроки используется отдельный метод, найденный на просторах интернета.
Однако, для парса используется String, а я столько начитался про то, что он съедает целиком плату Arduino и всё, что находится рядом с ней, что теперь у меня трясутся коленки.
Поэтому, если найдутся знатоки, которые помогут перевести этот код на сhar, буду очень признателен.

#include <SD.h>
#include <SPI.h>

#define NUMBER_OF_SENSORS 4
#define MAX_ROWS_NUMBER 100

File myFile;

void setup() {
  Serial.begin(9600);
  pinMode(40, OUTPUT);
  if (!SD.begin(40)) {
    while (1);
  }
  dataFromSDCard("200515.txt", 2);
}

void dataFromSDCard(String askDataFile, byte id) {

  uint8_t askHourFrom = 10;
  uint8_t askMinFrom = 30;
  uint8_t askHourTo = 20;
  uint8_t askMinTo = 30;

  uint16_t indexRow = 0;
  String askId = "id=" + String(id);
  String buffer1, buffer2;
  uint8_t resultDoW;
  uint8_t resultHour[MAX_ROWS_NUMBER];
  uint8_t resultMin[MAX_ROWS_NUMBER];
  int8_t resultSetTemp[MAX_ROWS_NUMBER];
  int8_t resultActTemp[MAX_ROWS_NUMBER];

  myFile = SD.open(askDataFile, FILE_READ);
  if (myFile) {
    while (myFile.available()) {
      buffer1 = myFile.readStringUntil('\n');
      buffer2 = getSplitFromString(buffer1, '#', 1);
      resultDoW = getSplitFromString(buffer2, ':' , 0).toInt();
      resultHour[indexRow] = getSplitFromString(buffer2, ':' , 1).toInt();
      resultMin[indexRow] = getSplitFromString(buffer2, ':' , 2).toInt();
      if (resultHour[indexRow] * 60 + resultMin[indexRow] >= askHourFrom * 60 + askMinFrom &&
          resultHour[indexRow] * 60 + resultMin[indexRow] <= askHourTo * 60 + askMinTo) {
        buffer2 = getSplitFromString(buffer1, '#', 2);
        if (buffer2.length() > 10) {
          for (byte b = 0; b < NUMBER_OF_SENSORS; b++) {
            buffer1 = getSplitFromString(buffer2, ';', b);
            if (getSplitFromString(buffer1, ':' , 0).equals(askId)) {
              char charSet[8];
              char charAct[8];
              getSplitFromString(buffer1, ':' , 1).toCharArray(charSet, 8);
              getSplitFromString(buffer1, ':' , 2).toCharArray(charAct, 8);
              resultSetTemp[indexRow] = round(atof(charSet));
              resultActTemp[indexRow] = round(atof(charAct));
              indexRow++;
              break;
            }
          }
        }
      }
    }
  }
 }
}

String getSplitFromString(String data, char separator, int index) {
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length() - 1;
  for (int i = 0; i <= maxIndex && found <= index; i++) {
    if (data.charAt(i) == separator || i == maxIndex) {
      found++;
      strIndex[0] = strIndex[1] + 1;
      strIndex[1] = (i == maxIndex) ? i + 1 : i;
    }
  }
  return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}

void loop() {
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Да, небось, всё не съест. Параметры только по значению не передавайте. Вот здесь почитайте, там прямо написано как и почему нужно передавать String.

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

KotBegemot пишет:

Данные нужны для построения графика температур на ПК по запросу, не регулярно. Файл пишется на SD карту.

Я бы реализовал это так - передавал бы данные при любой возможности на ПК и загружал в СУБД, а из СУБД строил бы любые отчеты. 

KotBegemot
Offline
Зарегистрирован: 15.05.2020

Спасибо, Евгений. Очень познавательно и весело )).
Код поправил - &.
"Фигурные скобки – великая вещь!"