Gestire file JSON con Arduino, esp32 ed esp8266

Spread the love

Quando si crea un data logging è importante strutturare i dati, a volte una buona soluzione può essere il formato JSON.

ArduinoJson

JavaScript Object Notation (JSON) è un formato di file standard aperto che utilizza testo leggibile dall’uomo per trasmettere oggetti di dati costituiti da coppie attributo-valore e tipi di dati array (o qualsiasi altro valore serializzabile). È un formato di dati molto comune utilizzato per la comunicazione asincrona browser-server, incluso un sostituto dell’XML in alcuni sistemi in stile AJAX.

JSON è un formato di dati indipendente dalla lingua. Deriva da JavaScript, ma a partire dal 2017 molti linguaggi di programmazione includono codice per generare e analizzare dati in formato JSON. Il tipo di supporto Internet ufficiale per JSON è application/json. I nomi di file JSON utilizzano l’estensione .json. (cit. wiki)

Ad esempio, se è necessario archiviare e aggiornare molte volte il valore di alcuni totali, è possibile creare una struttura come questa:

{
    "lastUpdate": "05/06/2019 06:50:57",
    "energyLifetime": 21698620,
    "energyYearly": 1363005,
    "energyMonthly": 58660,
    "energyWeekly": 41858,
    "energyDaily": 158
}

Ecco un esempio di log della tensione della batteria:

{
    "lastUpdate": "30/01/2019 21:24:34",
    "data": {
        "1004": 3.914468,
        "1024": 3.931694,
        "1044": 3.90479,
        "1104": 3.973645,
        "1124": 3.969726,
        "1144": 3.954823,
        "1204": 3.957871,
        "1224": 3.930581,
        "1244": 3.954048,
        "1304": 3.947516,
        "1324": 3.945629,
        "1344": 3.863081,
        "1404": 3.919597,
        "1424": 3.927387,
        "1444": 3.912968,
        "1504": 3.856597,
        "1524": 3.846629,
        "1544": 3.903871,
        "1604": 3.857226,
        "1624": 3.889839,
        "1644": 3.865693,
        "1704": 3.846145,
        "1724": 3.780726,
        "1744": 3.846677,
        "1804": 3.770323,
        "1824": 3.778887,
        "1844": 3.769597,
        "1904": 3.778693,
        "1924": 3.806177,
        "1944": 3.801145,
        "2004": 3.744049,
        "2024": 3.707661,
        "2044": 3.780871,
        "2104": 3.708484,
        "2124": 3.729726,
        "0003": 4.138742,
        "0023": 4.147887,
        "0043": 4.143387,
        "0103": 4.139806,
        "0123": 4.078258,
        "0143": 4.128,
        "0203": 4.107871,
        "0223": 4.066645,
        "0243": 4.103419,
        "0303": 4.082081,
        "0323": 4.126839,
        "0343": 4.118032,
        "0403": 4.096113,
        "0423": 4.110532,
        "0443": 4.099307,
        "0503": 4.013565,
        "0523": 4.089581,
        "0544": 4.075549,
        "0604": 4.025274,
        "0624": 4.067129,
        "0644": 3.997742,
        "0704": 3.987677,
        "0724": 3.981823,
        "0744": 4.006113,
        "0804": 4.0035,
        "0824": 3.966968,
        "0844": 4.016418,
        "0904": 3.969049,
        "0924": 4.002532,
        "0944": 3.907742
    }
}

Come puoi vedere è più leggibile del formato CSV o di altri formati ed è più versatile.

Library

Per i sistemi Arduino like esiste una libreria che può essere considerata uno standard, è possibile scaricarlo da github o dal Manage library dell’IDE Arduino.

Select library ArduinoJson on Arduino IDE

Per questa biblioteca esiste anche un sito, molto informativo.

How to

L’utilizzo è abbastanza semplice, la differenza rispetto alla versione precedente è che DynamicJsonDocument non ha più una gestione dinamica della memoria, quindi ora possiamo usare quell’oggetto per tutti (statico e dinamico).

const size_t capacity = 1024;
DynamicJsonDocument doc(capacity);

E’ importante calcolare la capacità è la dimensione massima del tuo file, per avere un’idea della dimensione che ti serve puoi controllare qui, È una semplice calcolatrice che dal file ti dà la dimensione relativa.

Per impostare la SD è possibile fare riferimento al mio articolo “Come utilizzare la scheda SD con esp8266 e Arduino”.

In questo esempio scriviamo un file così:

{
  "energyLifetime": 21698620,
  "energyYearly": 1363005
}

Una classica struttura di file di configurazione.

Aggiungo un commento su tutto il codice rilevante.

/*
 Write JSON file to SD
 {
   "energyLifetime": 21698620,
   "energyYearly": 1363005
 }
 by Mischianti Renzo <https://mischianti.org>

 https://www.mischianti.org/
*/

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

const int chipSelect = 4;

const char *filename = "/test.txt";  // <- SD library uses 8.3 filenames

// Prints the content of a file to the Serial
void printFile(const char *filename) {
  // Open file for reading
  File file = SD.open(filename);
  if (!file) {
    Serial.println(F("Failed to read file"));
    return;
  }

  // Extract each characters by one by one
  while (file.available()) {
    Serial.print((char)file.read());
  }
  Serial.println();

  // Close the file
  file.close();
}

void setup() {
  // Initialize serial port
  Serial.begin(9600);
  while (!Serial) continue;

  delay(500);

  // Initialize SD library
  while (!SD.begin(chipSelect)) {
    Serial.println(F("Failed to initialize SD library"));
    delay(1000);
  }

  SD.remove(filename);

  // Open file for writing
  File file = SD.open(filename, FILE_WRITE);
  if (!file) {
    Serial.println(F("Failed to create file"));
    return;
  }

  // Allocate a temporary JsonDocument
  // Don't forget to change the capacity to match your requirements.
  // Use arduinojson.org/v6/assistant to compute the capacity.
//  StaticJsonDocument<512> doc;
  // You can use DynamicJsonDocument as well
  DynamicJsonDocument doc(512);

  // Set the values in the document
  doc["energyLifetime"] = 21698620;
  doc["energyYearly"] = 1363005;


  // Serialize JSON to file
  if (serializeJson(doc, file) == 0) {
    Serial.println(F("Failed to write to file"));
  }

  // Close the file
  file.close();

  // Print test file
  Serial.println(F("Print test file..."));
  printFile(filename);
}

void loop() {
  // not used in this example
}

Genera un array di dati, e aggiunge un elemento ogni 5 secondi e aggiorna il file originale.

La struttura generata è così:

{
  "millis": 10735,
  "data": [
    {
      "prevNumOfElem": 1,
      "newNumOfElem": 2
    },
    {
      "prevNumOfElem": 2,
      "newNumOfElem": 3
    },
    {
      "prevNumOfElem": 3,
      "newNumOfElem": 4
    }
  ]
}

Dove millis viene sovrascritto e ogni volta appare un nuovo valore sull’array.

/*
 Write JSON file to SD
 by Mischianti Renzo <https://mischianti.org>

 https://www.mischianti.org/
 */

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

const int chipSelect = 4;

const char *filename = "/test.jso";  // <- SD library uses 8.3 filenames

// Prints the content of a file to the Serial
void printFile(const char *filename) {
	// Open file for reading
	File file = SD.open(filename);
	if (!file) {
		Serial.println(F("Failed to read file"));
		return;
	}

	// Extract each characters by one by one
	while (file.available()) {
		Serial.print((char) file.read());
	}
	Serial.println();

	// Close the file
	file.close();
}

void setup() {
	// Initialize serial port
	Serial.begin(9600);
	while (!Serial)
		continue;

	delay(500);

	// Initialize SD library
	while (!SD.begin(chipSelect)) {
		Serial.println(F("Failed to initialize SD library"));
		delay(1000);
	}

	Serial.println(F("SD library initialized"));

	Serial.println(F("Delete original file if exists!"));
	SD.remove(filename);

}

void loop() {
	// Allocate a temporary JsonDocument
	// Don't forget to change the capacity to match your requirements.
	// Use arduinojson.org/v6/assistant to compute the capacity.
	//  StaticJsonDocument<512> doc;
	// You can use DynamicJsonDocument as well
	DynamicJsonDocument doc(1024);

	JsonObject obj;
	// Open file
	File file = SD.open(filename);
	if (!file) {
		Serial.println(F("Failed to create file, probably not exists"));
		Serial.println(F("Create an empty one!"));
		obj = doc.to<JsonObject>();
	} else {

		DeserializationError error = deserializeJson(doc, file);
		if (error) {
			// if the file didn't open, print an error:
			Serial.println(F("Error parsing JSON "));
			Serial.println(error.c_str());

			// create an empty JSON object
			obj = doc.to<JsonObject>();
		} else {
			// GET THE ROOT OBJECT TO MANIPULATE
			obj = doc.as<JsonObject>();
		}

	}

	// close the file already loaded:
	file.close();

	obj[F("millis")] = millis();

	JsonArray data;
	// Check if exist the array
	if (!obj.containsKey(F("data"))) {
		Serial.println(F("Not find data array! Crete one!"));
		data = obj.createNestedArray(F("data"));
	} else {
		Serial.println(F("Find data array!"));
		data = obj[F("data")];
	}

	// create an object to add to the array
	JsonObject objArrayData = data.createNestedObject();

	objArrayData["prevNumOfElem"] = data.size();
	objArrayData["newNumOfElem"] = data.size() + 1;

	SD.remove(filename);

	// Open file for writing
	file = SD.open(filename, FILE_WRITE);

	// Serialize JSON to file
	if (serializeJson(doc, file) == 0) {
		Serial.println(F("Failed to write to file"));
	}

	// Close the file
	file.close();

	// Print test file
	Serial.println(F("Print test file..."));
	printFile(filename);

	delay(5000);
}

Ora organizziamo un pò il codice. Il codice in questo formato è inutilizzabile, ma con 2 semplici funzioni dovrebbe migliorare.

/*
 Write JSON file to SD
 by Renzo Mischianti <https://mischianti.org>

 https://www.mischianti.org/
 */

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

const int chipSelect = 4;

const char *filename = "/test.jso";  // <- SD library uses 8.3 filenames

File myFileSDCart;

/**
 * Function to deserialize file from SD
 * by Renzo Mischianti <https://mischianti.org>
 * example:
 *  DynamicJsonDocument doc(1024);
	JsonObject obj;
	obj = getJSonFromFile(&doc, filename);
 */
JsonObject getJSonFromFile(DynamicJsonDocument *doc, String filename, bool forceCleanONJsonError = true ) {
	// open the file for reading:
	myFileSDCart = SD.open(filename);
	if (myFileSDCart) {
		// read from the file until there's nothing else in it:
//			if (myFileSDCart.available()) {
//				firstWrite = false;
//			}

		DeserializationError error = deserializeJson(*doc, myFileSDCart);
		if (error) {
			// if the file didn't open, print an error:
			Serial.print(F("Error parsing JSON "));
			Serial.println(error.c_str());

			if (forceCleanONJsonError){
				return doc->to<JsonObject>();
			}
		}

		// close the file:
		myFileSDCart.close();

		return doc->as<JsonObject>();
	} else {
		// if the file didn't open, print an error:
		Serial.print(F("Error opening (or file not exists) "));
		Serial.println(filename);

		Serial.println(F("Empty json created"));
		return doc->to<JsonObject>();
	}

}

/**
 * Function to serialize file to SD
 * by Renzo Mischianti <https://mischianti.org>
 * example:
 * boolean isSaved = saveJSonToAFile(&doc, filename);
 */
bool saveJSonToAFile(DynamicJsonDocument *doc, String filename) {
	SD.remove(filename);

	// open the file. note that only one file can be open at a time,
	// so you have to close this one before opening another.
	Serial.println(F("Open file in write mode"));
	myFileSDCart = SD.open(filename, FILE_WRITE);
	if (myFileSDCart) {
		Serial.print(F("Filename --> "));
		Serial.println(filename);

		Serial.print(F("Start write..."));

		serializeJson(*doc, myFileSDCart);

		Serial.print(F("..."));
		// close the file:
		myFileSDCart.close();
		Serial.println(F("done."));

		return true;
	} else {
		// if the file didn't open, print an error:
		Serial.print(F("Error opening "));
		Serial.println(filename);

		return false;
	}
}

// Prints the content of a file to the Serial
void printFile(const char *filename) {
	// Open file for reading
	File file = SD.open(filename);
	if (!file) {
		Serial.println(F("Failed to read file"));
		return;
	}

	// Extract each characters by one by one
	while (file.available()) {
		Serial.print((char) file.read());
	}
	Serial.println();

	// Close the file
	file.close();
}

void setup() {
	// Initialize serial port
	Serial.begin(9600);
	while (!Serial)
		continue;

	delay(500);

	// Initialize SD library
	while (!SD.begin(chipSelect)) {
		Serial.println(F("Failed to initialize SD library"));
		delay(1000);
	}

	Serial.println(F("SD library initialized"));

	Serial.println(F("Delete original file if exists!"));
	SD.remove(filename);

}

void loop() {
	// Allocate a temporary JsonDocument
	// Don't forget to change the capacity to match your requirements.
	// Use arduinojson.org/v6/assistant to compute the capacity.
	//  StaticJsonDocument<512> doc;
	// You can use DynamicJsonDocument as well
	DynamicJsonDocument doc(1024);

	JsonObject obj;
	obj = getJSonFromFile(&doc, filename);

	obj[F("millis")] = millis();

	JsonArray data;
	// Check if exist the array
	if (!obj.containsKey(F("data"))) {
		Serial.println(F("Not find data array! Crete one!"));
		data = obj.createNestedArray(F("data"));
	} else {
		Serial.println(F("Find data array!"));
		data = obj[F("data")];
	}

	// create an object to add to the array
	JsonObject objArrayData = data.createNestedObject();

	objArrayData["prevNumOfElem"] = data.size();
	objArrayData["newNumOfElem"] = data.size() + 1;


	boolean isSaved = saveJSonToAFile(&doc, filename);

	if (isSaved){
		Serial.println("File saved!");
	}else{
		Serial.println("Error on save File!");
	}

	// Print test file
	Serial.println(F("Print test file..."));
	printFile(filename);

	delay(5000);
}

Ora penso che sia migliorato e che sia abbastanza chiaro.

Grazie


Spread the love

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *