Работа с массивами в ArduinoJSON v.6

К сожалению, для версии ArduinoJSON v.6, которая пока в бета-версии, очень мало примеров работы. Работа с ней отличается от V.5. Из-за скудной документации мне потребовалось некоорое время, чтобы разобраться, поэтому задокументирую свои находки.

В качестве примера возьму JSON файл конфигурации для Modbus.

[
  {
    "Slave": {
      "Connection": "ttyS0",
      "HwId": "TermoSensor-0a:01:01:01:01:02",
      "BaudRate": "9600",
      "DataBits": "8",
      "StopBits": "1",
      "Parity": "ODD",
      "FlowControl": "NONE",
      "Ops": [
        {
          "PollingInterval": "2000",
          "UnitId": "1",
          "Function": "0x04",
          "Address": "0x01",
          "Len": "1",
          "DisplayName": "Temp"
        },
        {
          "PollingInterval": "2000",
          "UnitId": "1",
          "Function": "0x04",
          "Address": "0x02",
          "Len": "1",
          "DisplayName": "Humidity"
        }          
      ]
    }
  },
  {
    "Slave": {
      "Connection": "192.168.1.2",
      "TcpPort": "502",
      "HwId": "TermoSensor-0a:01:01:01:01:02",
      "Ops": [
        {
          "PollingInterval": "2000",
          "UnitId": "1",
          "Function": "0x04",
          "Address": "0x01",
          "Len": "1",
          "DisplayName": "Temp"
        }  
      ]
    }
  }
]

Удобно визуализировать файл с помощью online JSON редактора.

JSON файл для описания конфигурации Modbus.

Чтобы вычитать массив из файла нужно использовать следующий код.

#include <ArduinoJson.h>
#include <ESP8266WiFi.h>

#define Num_of_Serials 2 //Number of serial ports on the board
#define Num_of_Ops     5 //Number of operations per connection
const size_t capacity = 2*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + Num_of_Ops*JSON_OBJECT_SIZE(6) + Num_of_Serials*JSON_OBJECT_SIZE(8) + 830;
StaticJsonDocument<capacity> doc;

char* rs485cfg = "/rs485cfg.txt";

void autoProcess(String json)
{
  //DynamicJsonDocument doc;
  DeserializationError error = deserializeJson(doc, json);
  if (error) {
    processError(error.code());
    return;
  }

  JsonObject obj = doc.as<JsonObject>();
  JsonArray arr = doc.as<JsonArray>();

  /*Serial.println("--------------------------------------"); 
  JsonArray::iterator it;
  for (it=arr.begin(); it!=arr.end(); ++it) {
    const JsonObject& elem = *it;
    JsonObject slave = elem["Slave"];
    printValue("Connection", slave["Connection"].as<String>());
  }*/

  Serial.println("--------------------------------------"); 
  for (const JsonObject& item : arr) {
    const JsonObject& slaves = item["Slave"];
    //printValue("Connection", slave["Connection"].as<String>());
    for (const JsonPair& slave : slaves) {
      String key = slave.key().c_str();
      if (slave.value().is<JsonArray>())
      {
        JsonArray operations = slave.value().as<JsonArray>();
        Serial.println("---Operations:-------------------------"); 
        for (const JsonObject& operation : operations) {
          for (const JsonPair& op : operation) {
            key = op.key().c_str();
            Serial.println("\t" + key + ": " + op.value().as<String>());
          }
          Serial.println("--------------------------------------"); 
        }
      }
      else
      {
        Serial.println(key + ": " + slave.value().as<String>());
      }
    }
  }
}

void processError(int error)
{
    switch (error) {
      case DeserializationError::Ok:
        Serial.print(F("Deserialization succeeded"));
        break;
      case DeserializationError::InvalidInput:
        Serial.print(F("Invalid input!"));
        break;
      case DeserializationError::NoMemory:
        Serial.print(F("Not enough memory"));
        break;
      default:
        Serial.print(F("Deserialization failed"));
        break;
    }
    return;
}

Сразу отмечу, что можно использовать DynamicJsonDocument:

DynamicJsonDocument doc(capacity);
vs
StaticJsonDocument<capacity> doc;

В примере используется StaticJsonDocument. Я не разобрался до конца с формулой для расчета capacity, поэтому дал ориентировочный вариант с приличным запасом. Уточню формулу позже. У ArduinoJSON есть сервис Assistant. Им можно воспользоваться для получения нужного размера. К сожалению, если JSON файл используется для конфигурирования, не известно сколько объектов в нем будет, поэтому приходится использовать некоторые предельные значения. Естественно, это понапрасну отъедает память. 🙁

Динамический вариант парсинга JSON массива интересен, но в случае конфигурационных файлов полезнее ручной вариант задания типов с перегрузкой в необходимые массивы структур или объектов. Рассмотрю вариант с динамическими массивами структур для размещения значений файла конфигурации протокола Modbus после парсинга JSON array.

typedef struct{
  int PollingInterval;
  int UnitId;
  int Function;
  int Address;
  int Len;
  String DisplayName;
} Operation;

typedef std::vector<Operation> OperationsType;

typedef struct{
  String Connection;
  String HwId;
  int BaudRate;
  int DataBits;
  int StopBits;
  String Parity;
  String FlowControl;
  int TcpPort;
  OperationsType Operations;
} Slave;

typedef std::vector<Slave> Slaves;

void manualProcess(String json)
{
  Slaves slaves;
    
  //DynamicJsonDocument doc;
  DeserializationError error = deserializeJson(doc, json);
  if (error) {
    processError(error.code());
    return;
  }

  JsonObject obj = doc.as<JsonObject>();
  JsonArray arr = doc.as<JsonArray>();

  //Serial.println("--------------------------------------"); 
  for (int i=0; i<arr.size(); i++) {
    JsonObject slave = arr[i]["Slave"];
    Slave slaveItem; 
    slaveItem.Connection = slave["Connection"].as<String>();
    slaveItem.HwId = slave["HwId"].as<String>();
    slaveItem.BaudRate = slave["BaudRate"].as<int>();
    slaveItem.DataBits = slave["DataBits"].as<int>();
    slaveItem.StopBits = slave["StopBits"].as<int>();
    slaveItem.Parity = slave["Parity"].as<String>();
    slaveItem.FlowControl = slave["FlowControl"].as<String>();
    slaveItem.TcpPort = slave["TcpPort"].as<int>();

    printValue("Connection", slaveItem.Connection);
    printValue("HwId", slaveItem.HwId);
    printValue("BaudRate", String(slaveItem.BaudRate));
    printValue("DataBits", String(slaveItem.DataBits));
    printValue("StopBits", String(slaveItem.StopBits));
    printValue("Parity", slaveItem.Parity);
    printValue("FlowControl", slaveItem.FlowControl);
    printValue("TcpPort", String(slaveItem.TcpPort));    

    JsonArray ops = arr[i]["Slave"]["Ops"];
    Operation operation;
    Serial.println("---Operations:-------------------------"); 
    for (int i=0; i<ops.size(); i++) {
      JsonObject op = ops[i];
      operation.PollingInterval = op["PollingInterval"].as<int>();
      operation.UnitId = op["UnitId"].as<int>();
      const char* function = op["Function"].as<char*>();
      operation.Function = StrToHex(function);//op["Function"].as<int>();
      const char* address = op["Address"].as<char*>();
      operation.Address = StrToHex(address);//op["Address"].as<int>();
      operation.Len = op["Len"].as<int>();
      operation.DisplayName = op["DisplayName"].as<String>();
      slaveItem.Operations.push_back(operation);

      printValue("\tPollingInterval", String(operation.PollingInterval));
      printValue("\tUnitId", String(operation.UnitId));
      printValue("\tFunction", String(operation.Function));
      printValue("\tAddress", String(operation.Address));
      printValue("\tLen", String(operation.Len));
      printValue("\tDisplayName", operation.DisplayName);
      Serial.println("--------------------------------------"); 
    }
    slaves.push_back(slaveItem);
  }
}

int StrToHex(const char* str)
{
  if (strchr(str, 'x') != NULL )
  { 
    return (int) strtol(str, 0, 16);
  }
  return (int) strtol(str, 0, 10);
}

void printValue(String name, String value)
{
  if ((value != "null") && (value != "") && (value != "0")) //exclude 0 - it's not so good idea. :-) 
  {
    Serial.println(name + ": " + value);
  }
}

В примере создается динамический массив структур с использованием шаблона vector.

typedef std::vector<Slave> Slaves;
typedef std::vector<Operation> OperationsType;

Поскольку в JSON нет информации о нужном типе, приведение приходится делать вручную:

slaveItem.StopBits = slave["StopBits"].as<int>();
 slaveItem.Parity = slave["Parity"].as<String>();

Думаю, разобраться с примерами кода будет несложно. Если возникнут замечания и предложения — пишите. 🙂

Spread the love
Запись опубликована в рубрике IT рецепты. Добавьте в закладки постоянную ссылку.

Добавить комментарий