Всем привет!
делаю один проект (домашняя автоматика), в котором html интерфейс работает на orange -pi и ведет обмен с ардуиной через UART
на ардуино обработка данных, снятие показаний с датчиков и отсылка обратно.
Получение данных я сделал через прерывание по UART, т.к. в этом случае прием работает идеально.
Данные получаются в строку формата String с названием inputString.
данные передаются по принципу:
-строка состоит из отдельных блоков, разделенных символом (у меня это '')
-каждый блок состоит из первых двух цифр (номер датчика/переменной), разделителя (‘I’) и -передаваемых данных, различной длины.
-первый блок имеет неизменный состав и служит для проверки того, что приняты именно данные от другого устройства. ($##)
в конце передаваемой строки в php (html) передается два кода - \n \r
количество передаваемых блоков может быть разным в процессе передачи.
те в основном идет передача “запроса” на обновление данных:
$##*
если нажимается какая-то кнопка, то к запросу добавляется информация об этой кнопке, тогда запрос выглядит так:
$##02I1
Если нажато две кнопки или надо передать какие-то данные, то вид будет примерно такой:
$##02I110I87* , где переменная 02 имеет значение 1, переменная 10 имеет значение 87… и тд.
Максимальное колличество передаваемых данных конечно, например 10.
предположим, что в теле программы подготовленны переменные:
byte Sensor_01 = 0;
…
byte Sensor_10 = 0;
Помогите написать код, который разберет уже принятую, готовую строку на составляющие элементы, а элементы разобрать на части и присвоить значения переменным.
Я видел на старом форуме отличные примеры (в тч от пользователя ЕвгенийП), но там разбор был побайтово в процессе получения.
String inputString = ""; // a String to hold incoming data
String temp = ""; // a String to hold incoming data
bool stringComplete = false; // whether the string is complete
char myCharStr [ ] = "";
int R1 = 0;
void setup() {
// initialize serial:
Serial.begin(115200);
// reserve 200 bytes for the inputString:
inputString.reserve(200);
}
void loop() {
//Serial.print("1");
// print the string when a newline arrives:
if (stringComplete) {
char charBuf[inputString.length()];
inputString.toCharArray(charBuf, inputString.length());
for (int i = 0; i < (inputString.length()-1); i++)
{
Serial.print(" ");
Serial.print(charBuf[i]);
}
Serial.println();
Serial.print("длина строки = "); // запятая для парсинга строки
Serial.println( inputString.length() ); // отсылаем флаг !!!последняя строчка должна быть println!!!
temp =charBuf[(inputString.length()-2)];
Serial.println(temp); // отсылаем флаг
Serial.print("*"); // запятая для парсинга строки
Serial.print(0); // отсылаем флаг !!!последняя строчка должна быть println!!!
Serial.print("*"); // запятая для парсинга строки
Serial.println(2); // отсылаем флаг !!!последняя строчка должна быть println!!!
/*
R1 = myCharStr[0];
Serial.print(R1); Serial.print(" ");
R1 = myCharStr[1];
Serial.print(R1); Serial.print(" ");
R1 = myCharStr[2];
Serial.print(R1); Serial.print(" ");
R1 = myCharStr[3];
Serial.print(R1); Serial.print(" ");
R1 = myCharStr[4];
Serial.print(R1); Serial.print(" ");
R1 = myCharStr[5];
Serial.println (R1); Serial.print(" "); */
Serial.println(inputString);
// clear the string:
inputString = "";
stringComplete = false;
}
}
/*
SerialEvent occurs whenever a new data comes in the hardware serial RX. This
routine is run between each time loop() runs, so using delay inside loop can
delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
while (Serial.available()) {
// get the new byte:
char inChar = (char)Serial.read();
// add it to the inputString:
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n') {
// if (inChar == 'a') {
stringComplete = true;
}
}
}
Так у вас же есть, как вы пишете, отличные примеры, которые читают байты из последовательного порта? - в чем сложность читать эти же байты из массива символов?
Но если хотите писать сами и работать с полной строкой - обратите внимание на функцию strtok()
разбиваете строку на блоки по разделителям, потом блоки - на пары “номер датчика”-“значение” и обрабатываете как хотите.
Можно еще заморочится с sscanf(), чтобы сразу преобразовывать текстовые данные в нужные типы, но (на мой взгляд), работа со сканф сложнее для понимания.
Евгений, здравствуйте! да, наверное, это самое простое решение. уже накидал кусок простенького кода, который разбивает строку. то, что гадит - буфер обновляется, думаю - не страшно. пока не знаю, как при разборе подстроки разделить номер переменной и ее значение…
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
char *str;
char sz[] = "$##*01I01*02I22*22I89999";
char *p = sz;
while ((str = strtok_r(p, "*", &p)) != NULL)
{
//Serial.println(str);
char *str1;
char *p1 = str;
while ((str1 = strtok_r(p1, "I", &p1)) != NULL)
{
Serial.println(str1);
}
}
delay(1000);
}
Очень просто: нужно представить себя на месте процессора - представьте, что именно Вам нужно разобрать строку. Как Вы это будете делать? Запишите свои действия по-русски, а потом просто переведите с русского на Си.
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
char *str;
char sz[] = "$##*01I01*02I22*22I89999";
char *p = sz;
while ((str = strtok_r(p, "*", &p)) != NULL)
{
Serial.println(str);
}
}
delay(1000);
}
вот код, он в цикле разбирает строку (для примера) $##01I0102I22*22I89999 на составляющие по разделителю * . В каждой итрации цикла я получаю массив char с именем str.
мне известно, что первые два элекмента этого массива - это число - номер датчика.
Все. что дальше символа I - это значение. те начиная с 3 элемента до коца - значение.
Как мне из первых двух элементов массива получить число для переменной?
В принципе определение номера датчика можно решить так:
int i = atoi(str);
Serial.print( "i =");
Serial.println(i);
при достижении до I функция прерывается и выдает то, что было до.
Как выделить в массиве остаточную часть и преобразовать ее тоже в число?
Ребята, посмотрите пожалуйста код.
вот набросал - вроде работает.
Нет ли явных косяков?
подстроку разбираю не по разделителю, а исходя из того, что первые два символа заведомо - номер датчика, а все остальное - данные.
String str22 = "xxx";
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
char *str;
char sz[] = "$##*99I01*02I22*22I99999*33I12345678*";
char *p = sz;
while ((str = strtok_r(p, "*", &p)) != NULL)
{
Serial.println(str);
//Serial.print( "strlen.str =");
//Serial.println(strlen(str));
if (str[2]=='I'){
char ID[2]=""; //Массив символов для хранения значения ID получаемых данных с кнопки
char DATA[20]=""; //Массив символов для хранения значения переменной получаемых от блока с индентификатором ID
ID[0] = str[0]; //Присваиваем первому байту массива ID первый байт массива всего блока полученной информации
ID[1] = str[1]; //Присваиваем второму байту массива ID второй байт массива всего блока полученной информации
byte n = 3; //переменная, обозначающая какой символ по счету в полученной строке является символом-разделителем (I)
byte input_id = atoi(ID); //Преобразуем массив символов ID в число и присваем это число переменной id
for (byte i = 0; i < (strlen(str)-n); i++) DATA[i] = str[i+n]; //Собираем массив символов DATA (значение переменной) из полученного блока начиная с n-го элемента и до конца.
uint32_t input_data = atol(DATA); //преобразование массива DATA в число и присвоение его переменной input_data. Для значений менее 32 767 использовать uint16_t input_data = atoi(DATA);
Serial.print("id=");
Serial.println(input_id);
Serial.print("DATA=");
Serial.println(input_data);
Serial.println(" ");
} else {Serial.println(" ");};
}
delay(100000);
}
что касается самого кода - слишком много лишних действий.
Зачем копировать символы id в отдельный массив, тем более делать это неправильно? Вы же сами написали, что функцией atoi() ID отлично извлекается прямо из готовой строки?
Зачем потом точно также копировать остальную строку в массив DATA? А на месте его использовать нельзя?
if (str[2]=='I'){
byte n = 3; //переменная, обозначающая какой символ по счету в полученной строке является символом-разделителем (I)
byte input_id = atoi(str); //Преобразуем массив символов ID в число и присваем это число переменной id
char* DATA = str + n;
uint32_t input_data = atol(DATA); //преобразование массива DATA в число
Serial.print("id=");
Serial.println(input_id);
Serial.print("DATA=");
Serial.println(input_data);
Serial.println(" ");
}
Число элементов в строковом массиве всегда должно быть на один больше, чем число символов.
Если у вас в строчке два символа, массив должен быть на три элемента.
В последнем элементе строкового массива должен быть числовой ноль, он еще обозначается как ‘\0’:
если у вас в массиве нет завершающего нуля, любая строковая функция С может работать некорректно. Наприер та же atoi() может выдать вместо ID полную чушь.
Первая строка (вся поступившая) вида ###12I1231323422I237* имеет особенность в том, что колличество блоков формата 12I123 (где 12 - номер переменной, датчика, I - разделитель, 123 - значение) может быть разным. В зависимости от изменившихся данных. количество датчиков (в моем случае кнопок на вэбстраничке) большое, нажимаются они редко и гонять одно и тоже - нет смысла.
По-этому строка сначала разбивается функцией strok в цикле while и получается всегда разное число итераций.
на выходе каждой итерации мы получаем блок данных формата 12I123. Если данные приняты правильно, то первые три символа будут всегда состоять из двух символов номера переменной и разделителя. Все, что дальше - значение.
Вот я и подумал, что если разбирать эту подсроку функцией strok с разделителем I , то она выведет произвольное число итераций (две в нашем случае), в первой будет выведен номер перменной, во второй - ее значение. Но для этого надо знать где номер, а где значение. Те в цикл надо ввести счетчик. и при значении счетчика = 1 присваивать номеру переменной текующую переменную, а при номере =2 значению опять текущую переменную.
Мне это показалось громоздким, те не упрощает код.
Поделитесь своими мыслями - прав ли я или можно сделать проще как-то?
Причем у меня появилась идея дальше. Разделитель подстроки I использовать разный.
те в моем случае при передаче из странички в ардуину просто значения переменной будет передаваться что-то типа 12I123, еще будут кнопки, передающие значения времени (для таймера). тогда разделитель можно использовать другой, например T: 13T14:34
И тд. те определяя значение разделителя if(str[3] == ‘T’)… можно разбирать подстроки по разной логике. В случае I сразу преобразовывать в переменную, а в случае с T выделять часы и минуты.
в процессе разбора общей строки циклом while мы получаем каждый раз переменную, содержащую что-то вида 12I123
если я сделаю atoi(12I123) , то на выходе получу числовое значение 12.
В принципе оно так работает, тк atoi на первом не числовом символе (I) прекратит создание числа. те сразу через atoi(str) можно получить номер переменной.
А вот со значением сложнее. Чтобы применить atoi к str для выявления значения, мне надо, чтобы он начал считать после I. для этого я создаю другой массив и заполняю его обрезанным массивом str начиная после разделителя I
просто по-другому не догадался как сделать. Если есть способ начать atoi с конкретного символа - напишите пожалуйста.
Вы на каждое сообщение отдельно отвечаете, что ли? Сначала прочтите все до конца - может и вопросов меньше будет.
Я вам написал полный рабочий код в сообщении 14