Для реализации одного из наших проектов нам понадобилось найти способы наладить взаимодействие между «взрослыми» серверами, служащих для накопления и обработки больших массивов информации и нашим устройством с микроконтроллером «STM32», эту самую информацию собирающим. Одним из вопросов, возникающих при проектировании радиоэлектронных систем, является передача данных (например, первоначальных настроек) от сервера к контроллеру.
Метод передачи данных, разумеется, может разниться в зависимости от решаемой задачи. При больших объемах информации, возможно, будет предпочтительнее использовать какой-то бинарный протокол, возможно, даже с сжатием информации. В случае же, если объем передаваемой информации относительно небольшой, можно передавать данные в формате JSON. В плюсы такому методу можно записать как удобство интерпретации передаваемой информации «на глаз», так как это практически просто удобочитаемый текстовый поток, так и простоту обработки при помощи готовых библиотек.
Давайте реализуем прием информации в формате JSON устройством на базе STM32 (конкретно, на STM32F107RB). Для меньшего расхода ресурсов микроконтроллера будем использовать JSON парсер JSMN. Конечно, STM32 потянет и куда более тяжелый софт, но, учитывая то, что JSON парсинг, как правило, отнюдь не основная задача микроконтроллера, не будем отъедать ресурсов более необходимого и используем наиболее легковесное решение.
Парсер JSMN несколько непривычен тем, что превращает обрабатываемую JSON строку не в набор готовых пар ключ-значение, а в набор ссылок — номеров символов в общей строке, откуда можно взять как наименование ключа, так и его значение.
Давайте попробуем считать с сервера JSON строку, обработать ее JSON парсером JSMN и, используя полученные данные, сформировать «нормальные» пары ключ-значение.
Для примера обрабатывать будем вот такую JSON строку, которая нужна была нам для настройки сетевых параметров нашего устройства:
Java Code:
{"REQUEST":"SET","PARAMETR":"Device","NAME":"VE-MS001","RS485ADR":0,"IP_ADDRESS":[192,168,1,254],"NETMASK":[255,255,255,0],"GATEWAY":[192,168,1,1]}
Итак, скачиваем и подключаем к своему проекту собственно парсер JSMN. Проблем быть не должно, так как весь парсер умещается всего в двух файлах, jsmn.c и jsmn.h, все остальное — документация, примеры использования и тесты.
Создаем структуру, в которую в конце концов попадут все декодированные данные:
C++ Code:
#include "jsmn.h" #define MAXANSWERLENGTH 256 #define MAXNUMBER_OF_TOKENS 32 #define STR_LENGTH 8 #define NUM_LENGTH 15 typedef struct { uint8_t REQUEST[STR_LENGTH]; uint8_t PARAMETR[STR_LENGTH]; uint8_t NAME[STR_LENGTH]; uint8_t RS485ADR[NUM_LENGTH]; uint8_t IP_ADDRESS[STR_LENGTH]; uint8_t NETMASK[STR_LENGTH]; uint8_t GATEWAY[STR_LENGTH]; }tmStruct; tmStruct RAMStruct;
Подготавливаем переменные для парсера:
C++ Code:
int resultCode; jsmn_parser p; jsmntok_t tokens[MAXNUMBER_OF_TOKENS];
Натравливаем JSON парсер на наши данные:
C++ Code:
unsigned char ptr[]="{\"REQUEST\":\"SET\",\"PARAMETR\":\"Device\",\"NAME\":\"VE-MS001\",\"RS485ADR\":0,\"IP_ADDRESS\":[192,168,1,254],\"NETMASK\":[255,255,255,0],\"GATEWAY\":[192,168,1,1]}"; unsigned int len=sizeof(ptr); jsmn_init(&p); resultCode = jsmn_parse(&p, ptr, len, tokens, sizeof(tokens) / sizeof(tokens[0])); if (resultCode > 0) { char keyString[MAX_TOKEN_LENGTH]; char Prev_keyString[MAX_TOKEN_LENGTH]; for (int i = 1; i <= resultCode - 1; i++) // resultCode == 0 => whole json string { jsmntok_t key = tokens[i]; uint16_t length = key.end - key.start; if (length < MAX_TOKEN_LENGTH) { memcpy(keyString, &ptr[key.start], length); keyString[length] = '\0'; if (strcmp(Prev_keyString, "REQUEST") == 0) strcpy(RAMStruct.REQUEST, keyString); else if (strcmp(Prev_keyString, "PARAMETR") == 0) strcpy(RAMStruct.PARAMETR, keyString); else if (strcmp(Prev_keyString, "NAME") == 0) strcpy(RAMStruct.Name, keyString); else if (strcmp(Prev_keyString, "RS485ADR") == 0) strcpy(RAMStruct.RS485ADR, keyString); else if (strcmp(Prev_keyString, "IP_ADDRESS") == 0) strcpy(RAMStruct.IP_ADDRESS, keyString); else if (strcmp(Prev_keyString, "NETMASK") == 0) strcpy(RAMStruct.NETMASK, keyString); else if (strcmp(Prev_keyString, "GATEWAY") == 0) strcpy(RAMStruct.GATEWAY, keyString); strcpy(Prev_keyString, keyString); } } }
В результате всех этих программных манипуляций в отладчике вы должны увидеть примерно следующую картину:
Сразу же возникает закономерный вопрос: а как же сформировать JSON в ответ? Тут ситуация еще проще. Нам поможет библиотека под названием JWrite. Вот пример, формирующий строку с текущими настройками нашего устройства:
C++ Code:
#include "jWrite.h" unsigned char ptr[MAXANSWERLENGTH]; if(type==1) //Answer Device { jwOpen( ptr, MAXANSWERLENGTH, JW_OBJECT, JW_PRETTY ); // open root node as object jwObj_string("ANSWER", "Device"); // writes "key":"value" jwObj_string("NAME", device.name); // writes "key":"value" jwObj_int("SENSORS", device.number_sensors); // writes "int":1 jwObj_int("RS485_ADRESS", device.rs485_addr); // writes "int":1 jwObj_array("IP_ADDRESS"); // start "anArray": [...] jwArr_int(device.ip_addr[0]); // add a few integers to the array jwArr_int(device.ip_addr[1]); jwArr_int(device.ip_addr[2]); jwArr_int(device.ip_addr[3]); jwEnd(); jwObj_array("NETMASK"); // start "anArray": [...] jwArr_int(device.netmask[0]); // add a few integers to the array jwArr_int(device.netmask[1]); jwArr_int(device.netmask[2]); jwArr_int(device.netmask[3]); jwEnd(); jwObj_array("GATEWAY"); // start "anArray": [...] jwArr_int(device.gw[0]); // add a few integers to the array jwArr_int(device.gw[1]); jwArr_int(device.gw[2]); jwArr_int(device.gw[3]); jwEnd(); // end the array resultCode=jwClose(); }
Результатом этих действий будет являться следущая строка с JSON:
Java Code:
{"ANSWER":"Device","NAME":"VE-MS001","SENSORS":2,"RS485_ADRESS":0,"IP_ADDRESS":[192,168,1,254],"NETMASK":[255,255,255,0],"GATEWAY":[192,168,1,1]}