bblanchon / ArduinoJson

📟 JSON library for Arduino and embedded C++. Simple and efficient.
https://arduinojson.org
MIT License
6.7k stars 1.12k forks source link

Example request for WiFiNINA #2065

Closed wittrup closed 6 months ago

wittrup commented 7 months ago

I've merged together a working example of JsonConfigFile.ino and WiFiStorage.ino so that ArduinoJson can be used with Arduino Nano 33 IoT and others that use u-blox NINA-W102 (datasheet).

I'd like to have it improved, quality checked etc. and implemented as an example in ArduinoJson/examples/

// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2024, Benoit Blanchon
// MIT License
//
// This example demonstrates storing project configuration in a file and interacting with NINA internal memory partition.
// APIs are modeled on SerialFlash library (not on SD) for faster operations and buffer avoidance.
//
// The file contains a JSON document structured as:
// {
//   "hostname": "examples.com",
//   "port": 2731
// }
//
// For more details, refer: https://arduinojson.org/v7/example/config/

#include <WiFiNINA.h>
#include <ArduinoJson.h>

// Configuration structure
struct Config {
  char hostname[64];
  int port;
};

const char* filename = "/fs/config.txt"; // File path
Config config;                           // Global configuration object

// Load configuration from file
void loadConfiguration(const char* filename, Config& config) {
  WiFiStorageFile file = WiFiStorage.open(filename); // Open file

  char file_buffer[1024]; // Buffer to read file content

  int n = 0; // Counter for file buffer
  if (file) {
    file.seek(0);
    while (file.available()) {
      uint8_t buf[128];
      int ret = file.read(buf, 128);
      for (size_t i = 0; i < ret; i++) {
        file_buffer[n] = buf[i];
        n++;
      }
    }
  }

  // Deserialize JSON document
  JsonDocument doc;
  DeserializationError error = deserializeJson(doc, file_buffer);
  if (error)
    Serial.println(F("Failed to read file, using default configuration"));

  // Copy values from JsonDocument to Config
  config.port = doc["port"] | 2731; // Default port if not specified
  strlcpy(config.hostname, doc["hostname"] | "example.com", sizeof(config.hostname)); // Default hostname if not specified

  file.close(); // Close file
}

// Save configuration to file
void saveConfiguration(const char* filename, const Config& config) {
  WiFiStorageFile file = WiFiStorage.open(filename); // Open file for writing

  if (file) {
    file.erase(); // Erase existing file to overwrite
  }

  // Create a JSON document
  JsonDocument doc;
  doc["hostname"] = config.hostname; // Set hostname
  doc["port"] = config.port; // Set port

  // Serialize JSON to string
  String file_buffer;
  if (serializeJson(doc, file_buffer) == 0) {
    Serial.println(F("Failed to write to file_buffer"));
  } else {
    Serial.print(F("Write to file_buffer success: "));
    Serial.println(file_buffer);
    file.write(file_buffer.c_str(), file_buffer.length());
  }

  file.close(); // Close file
}

// Print file content to Serial
void printFile(const char* filename) {
  WiFiStorageFile file = WiFiStorage.open(filename); // Open file for reading

  if (!file) {
    Serial.print("Failed to read file: ");
    Serial.println(filename);
  } else {
    if (file) {
      file.seek(0);
      while (file.available()) {
        uint8_t buf[128];
        int ret = file.read(buf, 128);
        Serial.write(buf, ret); // Print content
      }
      Serial.println("");
    }
    file.close(); // Close file
  }
}

void setup() {
  Serial.begin(115200);
  while ((!Serial) && (millis() < 3000)); // Wait until 3 seconds for Serial

  // Load default config if running for the first time
  Serial.println(F("Loading configuration..."));
  loadConfiguration(filename, config);

  // Save configuration
  Serial.println(F("Saving configuration..."));
  saveConfiguration(filename, config);

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

void loop() {
  // Not used in this example
}
bblanchon commented 7 months ago

Hi @wittrup,

I'm surprised you had to use buffers. Did you try to pass the file directly to deserializeJson() and serializeJson()?

Best regards, Benoit

wittrup commented 7 months ago

Yes.

Reproduced what I did first:

// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2024, Benoit BLANCHON
// MIT License
//
// This example shows how to store your project configuration in a file.
// It uses the SD library but can be easily modified for any other file-system.
//
// The file contains a JSON document with the following content:
// {
//   "hostname": "examples.com",
//   "port": 2731
// }
//
//
// https://arduinojson.org/v7/example/config/

#include <WiFiNINA.h>
#include <ArduinoJson.h>

// Our configuration structure.
struct Config {
  char hostname[64];
  int port;
};

const char* filename = "/fs/config.txt";  // File path
Config config;                            // <- global configuration object

// Loads the configuration from a file
void loadConfiguration(const char* filename, Config& config) {
  // Open file for reading
  WiFiStorageFile file = WiFiStorage.open(filename); // Open file for reading

  // Allocate a temporary JsonDocument
  JsonDocument doc;

  // Deserialize the JSON document
  DeserializationError error = deserializeJson(doc, file);
  if (error)
    Serial.println(F("Failed to read file, using default configuration"));

  // Copy values from the JsonDocument to the Config
  config.port = doc["port"] | 2731;
  strlcpy(config.hostname,                  // <- destination
          doc["hostname"] | "example.com",  // <- source
          sizeof(config.hostname));         // <- destination's capacity

  // Close the file (Curiously, File's destructor doesn't close the file)
  file.close();
}

// Saves the configuration to a file
void saveConfiguration(const char* filename, const Config& config) {
  // Open file for writing
  WiFiStorageFile file = WiFiStorage.open(filename); // Open file

  if (file) {
    file.erase(); // Erase existing file to overwrite
  }

  if (!file) {
    Serial.println(F("Failed to create file"));
    return;
  }

  // Allocate a temporary JsonDocument
  JsonDocument doc;

  // Set the values in the document
  doc["hostname"] = config.hostname;
  doc["port"] = config.port;

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

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

// Prints the content of a file to the Serial
void printFile(const char* filename) {
  // Open file for reading
  WiFiStorageFile file = WiFiStorage.open(filename); // Open file for reading
  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(115200);
  while ((!Serial) && (millis() < 3000)); // Wait until 3 seconds for Serial

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

  // Should load default config if run for the first time
  Serial.println(F("Loading configuration..."));
  loadConfiguration(filename, config);

  // Create configuration file
  Serial.println(F("Saving configuration..."));
  saveConfiguration(filename, config);

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

Verify generates the output:


\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino: In function 'void printFile(const char*)':
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:94:34: error: no matching function for call to 'WiFiStorageFile::read()'
In file included from \Documents\Arduino\libraries\WiFiNINA\src/WiFi.h:38:0,
                 from \Documents\Arduino\libraries\WiFiNINA\src/WiFiNINA.h:23,
                 from \AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:17:
\Documents\Arduino\libraries\WiFiNINA\src/WiFiStorage.h:108:11: note: candidate: uint32_t WiFiStorageFile::read(void*, uint32_t)
  uint32_t read(void *buf, uint32_t rdlen) {
           ^~~~
\Documents\Arduino\libraries\WiFiNINA\src/WiFiStorage.h:108:11: note:   candidate expects 2 arguments, 0 provided
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino: In function 'void setup()':
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:107:11: error: 'SD' was not declared in this scope
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:107:11: note: suggested alternative: 'SDA'
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:107:20: error: 'chipSelect' was not declared in this scope
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:107:20: note: suggested alternative: 'pselect'
In file included from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Deserialization/deserialize.hpp:9:0,
                 from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonDeserializer.hpp:7,
                 from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson.hpp:47,
                 from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson.h:9,
                 from \AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:18:
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Deserialization/Reader.hpp: In instantiation of 'int ArduinoJson::V703PB2::detail::Reader<TSource, Enable>::read() [with TSource = WiFiStorageFile; Enable = void]':
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/Latch.hpp:38:9:   required from 'void ArduinoJson::V703PB2::detail::Latch<TReader>::load() [with TReader = ArduinoJson::V703PB2::detail::Reader<WiFiStorageFile, void>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/Latch.hpp:30:11:   required from 'char ArduinoJson::V703PB2::detail::Latch<TReader>::current() [with TReader = ArduinoJson::V703PB2::detail::Reader<WiFiStorageFile, void>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonDeserializer.hpp:47:27:   required from 'char ArduinoJson::V703PB2::detail::JsonDeserializer<TReader>::current() [with TReader = ArduinoJson::V703PB2::detail::Reader<WiFiStorageFile, void>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonDeserializer.hpp:71:20:   required from 'ArduinoJson::V703PB2::DeserializationError::Code ArduinoJson::V703PB2::detail::JsonDeserializer<TReader>::parseVariant(ArduinoJson::V703PB2::detail::VariantData&, TFilter, ArduinoJson::V703PB2::DeserializationOption::NestingLimit) [with TFilter = ArduinoJson::V703PB2::detail::AllowAllFilter; TReader = ArduinoJson::V703PB2::detail::Reader<WiFiStorageFile, void>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonDeserializer.hpp:35:23:   required from 'ArduinoJson::V703PB2::DeserializationError ArduinoJson::V703PB2::detail::JsonDeserializer<TReader>::parse(ArduinoJson::V703PB2::detail::VariantData&, TFilter, ArduinoJson::V703PB2::DeserializationOption::NestingLimit) [with TFilter = ArduinoJson::V703PB2::detail::AllowAllFilter; TReader = ArduinoJson::V703PB2::detail::Reader<WiFiStorageFile, void>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Deserialization/deserialize.hpp:57:8:   required from 'ArduinoJson::V703PB2::DeserializationError ArduinoJson::V703PB2::detail::doDeserialize(TDestination&&, TReader, TOptions) [with TDeserializer = ArduinoJson::V703PB2::detail::JsonDeserializer; TDestination = ArduinoJson::V703PB2::JsonDocument&; TReader = ArduinoJson::V703PB2::detail::Reader<WiFiStorageFile, void>; TOptions = ArduinoJson::V703PB2::detail::DeserializationOptions<ArduinoJson::V703PB2::detail::AllowAllFilter>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Deserialization/deserialize.hpp:69:38:   required from 'ArduinoJson::V703PB2::DeserializationError ArduinoJson::V703PB2::detail::deserialize(TDestination&&, TStream&&, Args ...) [with TDeserializer = ArduinoJson::V703PB2::detail::JsonDeserializer; TDestination = ArduinoJson::V703PB2::JsonDocument&; TStream = WiFiStorageFile&; Args = {}; <template-parameter-1-5> = void]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonDeserializer.hpp:679:39:   required from 'typename ArduinoJson::V703PB2::detail::enable_if<ArduinoJson::V703PB2::detail::is_deserialize_destination<TDestination>::value, ArduinoJson::V703PB2::DeserializationError>::type ArduinoJson::V703PB2::deserializeJson(TDestination&&, Args&& ...) [with TDestination = ArduinoJson::V703PB2::JsonDocument&; Args = {WiFiStorageFile&}; typename ArduinoJson::V703PB2::detail::enable_if<ArduinoJson::V703PB2::detail::is_deserialize_destination<TDestination>::value, ArduinoJson::V703PB2::DeserializationError>::type = ArduinoJson::V703PB2::DeserializationError]'
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:39:57:   required from here
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Deserialization/Reader.hpp:22:26: error: no matching function for call to 'WiFiStorageFile::read()'
     return source_->read();  // Error here? See https://arduinojson.org/v7/invalid-input/
                          ^
In file included from \Documents\Arduino\libraries\WiFiNINA\src/WiFi.h:38:0,
                 from \Documents\Arduino\libraries\WiFiNINA\src/WiFiNINA.h:23,
                 from \AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:17:
\Documents\Arduino\libraries\WiFiNINA\src/WiFiStorage.h:108:11: note: candidate: uint32_t WiFiStorageFile::read(void*, uint32_t)
  uint32_t read(void *buf, uint32_t rdlen) {
           ^~~~
\Documents\Arduino\libraries\WiFiNINA\src/WiFiStorage.h:108:11: note:   candidate expects 2 arguments, 0 provided
In file included from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Serialization/serialize.hpp:7:0,
                 from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonSerializer.hpp:9,
                 from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Variant/ConverterImpl.hpp:7,
                 from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson.hpp:42,
                 from \Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson.h:9,
                 from \AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:18:
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Serialization/Writer.hpp: In instantiation of 'size_t ArduinoJson::V703PB2::detail::Writer<TDestination, Enable>::write(uint8_t) [with TDestination = WiFiStorageFile; Enable = void; size_t = unsigned int; uint8_t = unsigned char]':
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Serialization/CountingDecorator.hpp:17:12:   required from 'void ArduinoJson::V703PB2::detail::CountingDecorator<TWriter>::write(uint8_t) [with TWriter = ArduinoJson::V703PB2::detail::Writer<WiFiStorageFile, void>; uint8_t = unsigned char]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/TextFormatter.hpp:166:5:   required from 'void ArduinoJson::V703PB2::detail::TextFormatter<TWriter>::writeRaw(char) [with TWriter = ArduinoJson::V703PB2::detail::Writer<WiFiStorageFile, void>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/TextFormatter.hpp:85:15:   required from 'void ArduinoJson::V703PB2::detail::TextFormatter<TWriter>::writeFloat(T) [with T = double; TWriter = ArduinoJson::V703PB2::detail::Writer<WiFiStorageFile, void>]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonSerializer.hpp:65:5:   required from 'size_t ArduinoJson::V703PB2::detail::JsonSerializer<TWriter>::visit(ArduinoJson::V703PB2::JsonFloat) [with TWriter = ArduinoJson::V703PB2::detail::Writer<WiFiStorageFile, void>; size_t = unsigned int; ArduinoJson::V703PB2::JsonFloat = double]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Variant/VariantData.hpp:31:44:   required from 'typename TVisitor::result_type ArduinoJson::V703PB2::detail::VariantData::accept(TVisitor&) const [with TVisitor = ArduinoJson::V703PB2::detail::JsonSerializer<ArduinoJson::V703PB2::detail::Writer<WiFiStorageFile, void> >; typename TVisitor::result_type = unsigned int]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Variant/VariantData.hpp:69:31:   required from 'static typename TVisitor::result_type ArduinoJson::V703PB2::detail::VariantData::accept(const ArduinoJson::V703PB2::detail::VariantData*, TVisitor&) [with TVisitor = ArduinoJson::V703PB2::detail::JsonSerializer<ArduinoJson::V703PB2::detail::Writer<WiFiStorageFile, void> >; typename TVisitor::result_type = unsigned int]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Serialization/serialize.hpp:15:29:   required from 'size_t ArduinoJson::V703PB2::detail::doSerialize(ArduinoJson::V703PB2::JsonVariantConst, TWriter) [with TSerializer = ArduinoJson::V703PB2::detail::JsonSerializer; TWriter = ArduinoJson::V703PB2::detail::Writer<WiFiStorageFile, void>; size_t = unsigned int]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Serialization/serialize.hpp:22:34:   required from 'size_t ArduinoJson::V703PB2::detail::serialize(ArduinoJson::V703PB2::JsonVariantConst, TDestination&) [with TSerializer = ArduinoJson::V703PB2::detail::JsonSerializer; TDestination = WiFiStorageFile; size_t = unsigned int]'
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Json/JsonSerializer.hpp:133:35:   required from 'size_t ArduinoJson::V703PB2::serializeJson(ArduinoJson::V703PB2::JsonVariantConst, TDestination&) [with TDestination = WiFiStorageFile; size_t = unsigned int]'
\AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:75:30:   required from here
\Documents\Arduino\libraries\ArduinoJson\src/ArduinoJson/Serialization/Writer.hpp:18:26: error: no matching function for call to 'WiFiStorageFile::write(uint8_t&)'
     return dest_->write(c);
                          ^
In file included from \Documents\Arduino\libraries\WiFiNINA\src/WiFi.h:38:0,
                 from \Documents\Arduino\libraries\WiFiNINA\src/WiFiNINA.h:23,
                 from \AppData\Local\Temp\.arduinoIDE-unsaved202427-23644-rm19e1.fd93\JsonConfigFile\JsonConfigFile.ino:17:
\Documents\Arduino\libraries\WiFiNINA\src/WiFiStorage.h:117:11: note: candidate: uint32_t WiFiStorageFile::write(const void*, uint32_t)
  uint32_t write(const void *buf, uint32_t wrlen) {
           ^~~~~
\Documents\Arduino\libraries\WiFiNINA\src/WiFiStorage.h:117:11: note:   candidate expects 2 arguments, 1 provided

exit status 1

Compilation error: no matching function for call to 'WiFiStorageFile::read()'
bblanchon commented 6 months ago

I'm sorry, I didn't realize that WiFiStorageFile doesn't implement the Stream interface. I opened the following issue: arduino-libraries/WiFiNINA#276

bblanchon commented 6 months ago

Once WiFiStorageFile implements Stream, using it with ArduinoJson will be very straightforward. Not need to add a new example or a new documentation page.