Using OpenLog to update Particle Firmware

Hi,

I'm trying to use OpenLog to update firmware from an SD Card. I'm using a P-Series device and I'm running into a snag that could probably be (or has been) solved by someone with a better understanding of the Particle system firmware. Thanks in advance!

What has been done so far:

  • Sparkfun's OpenLog is setup
  • I can read and write to an SD card from a photon using the OpenLog API instead of SdFat (I can't' use TXRX because of how our PCB is setup)

What I'm trying to do/stuck on:

I'm trying to adapt @rickkas7's sdfirmwareflash script to work within our setup – work without using SdFat's API. It looks like the file with the firmware on it, firmwareFile, is being buffered here, and being instantiated here with SdFat's File class.

Instantiation:

File firmwareFile;

Buffer:

if (firmwareFile.read(buf, file.chunk_size) < 0) {

I'm pretty sure that given our setup, we can't read the file using SdFat, but instead need to rely on OpenLog's commands. Something like this method, readFile (below, at bottom).

So, how to buffer the file if it is being read from the SD card character by character? My assumption is that it I could save the characters to a variable and add them to the buffer, but I'm not quite sure how to do that. Any tricks?

In the following example on post #14 on How to get access to firmware flashing functions? - #14 by rickkas7 it looks like bytes are individually saved from tinker, without reading a buffer using SdFat. How can this be done with characters?




readFile:

//Reads the contents of a given file and dumps it to the serial terminal
//This function assumes the OpenLog is in command mode
void readFile(char *fileName) {

  while(OpenLog.available()) OpenLog.read(); //Clear incoming buffer

  OpenLog.print("read ");
  OpenLog.print(fileName);
  OpenLog.write(13); //This is \r

  //The OpenLog echos the commands we send it by default so we have 'read log823.txt\r' sitting
  //in the RX buffer. Let's try to not print this.
  while (1) {
    if (OpenLog.available())
      if (OpenLog.read() == '\r') break;
  }

  Serial.println("Reading from file:");

  //This will listen for characters coming from OpenLog and print them to the terminal
  //This relies heavily on the SoftSerial buffer not overrunning. This will probably not work
  //above 38400bps.
  //This loop will stop listening after 1 second of no characters received
  for (int timeOut = 0 ; timeOut < 1000 ; timeOut++) {
    while (OpenLog.available()) {
      char tempString[100];

      int spot = 0;
      while (OpenLog.available()) {
        tempString[spot++] = OpenLog.read();
        if (spot > 98) break;
      }
      tempString[spot] = '\0';
      Serial.write(tempString); //Take the string from OpenLog and push it to the Arduino terminal
      timeOut = 0;
    }

    delay(1);
  }

  //This is not perfect. The above loop will print the '.'s from the log file. These are the two escape characters
  //recorded before the third escape character is seen.
  //It will also print the '>' character. This is the OpenLog telling us it is done reading the file.

  //This function leaves OpenLog in command mode
}

For the controller there is not distiction between bytes (uint8_t) and characters (char) hence there is no difference in how to use them.
The only thing you may need to do is called type casting to make a function that expects one type accept another.

e.g.

  char tempString[100]; // this should be the length of the file.chunk_size 
  // fill array
  result = Spark_Save_Firmware_Chunk(file, (uint8_t*)temString, NULL);

What OpenLog library are you using?
The SparkfunOpenLog library in the Particle library repo has some functions to do that in one call

    void read(uint8_t* userBuffer, uint16_t bufferSize, String fileName); //Read the contents of a file into the provided buffer
    void read(uint8_t* userBuffer, uint16_t bufferSize, String fileName, uint16_t startingSpot);
1 Like

Thanks, @ScruffR! I’m going to look into this and get repost in a few!

Also, my situation is aligned with Porting OpenLog Library. I only have access to UART, so I don’t think OpenLog’s read method is available to me. Unless there is something that I’m missing?

Hi @ScruffR. Here’s a stab at it, below. Let me know what makes/doesn’t make sense! I still am clunky with c++ types :confused:

Major points are:

Reading and returning the entire file

// Saving as char*. What type makes sense here?
  char* file;
...
  file += tempString;
...
  return file;

Storing file as char*

char* tempFile = readFile(firmware_file_name);

Saving chunk

result = Spark_Save_Firmware_Chunk(file, (uint8_t*)tempFile[offset], NULL);

All of it

#include "Particle.h"
#include "system_update.h"

#include "UpdateFirmware.h"
STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));
// This is set up to use primary SPI with DMA
// SD Adapter   Electron
// SS (CS)      A2
// SCK          A3
// MISO (DO)    A4
// MOSI (DI)    A5

void checkCardForUpdates();
void updateFromFile();
void readFile();
void getFileSize();
void gotoCommandMode();
bool sdIsAvailable();

const unsigned long CARD_CHECK_PERIOD = 30000;
unsigned long lastCardCheck = 5000 - CARD_CHECK_PERIOD;
const char firmware_file_name = "firmware.bin";

void UpdateFirmware::poll() {
  if (millis() - lastCardCheck >= CARD_CHECK_PERIOD) {
		lastCardCheck = millis();
		checkCardForUpdates();
	}
}

//Reads the contents of a given file and dumps it to the serial terminal
//This function assumes the OpenLog is in command mode
void readFile(char *fileName) {

  //Old way
  OpenLog.print("read ");
  OpenLog.print(fileName);
  OpenLog.write(13); //This is \r

  //The OpenLog echos the commands we send it by default so we have 'read log823.txt\r' sitting
  //in the RX buffer. Let's try to not print this.
  while(1) {
    if(OpenLog.available())
      if(OpenLog.read() == '\r') break;
  }

  // Saving as char*. What type makes sense here?
  char* file;

  //This will listen for characters coming from OpenLog and print them to the terminal
  //This relies heavily on the SoftSerial buffer not overrunning. This will probably not work
  //above 38400bps.
  //This loop will stop listening after 1 second of no characters received
  for(int timeOut = 0 ; timeOut < 1000 ; timeOut++) {
    while(OpenLog.available()) {
      char tempString[100];

      int spot = 0;
      while(OpenLog.available()) {
        tempString[spot++] = OpenLog.read();
        if(spot > 98) break;
      }
      tempString[spot] = '\0';
      file += tempString;
      // Replacing Serial.write with an addition to the string
      // Serial.write(tempString); //Take the string from OpenLog and push it to the Arduino terminal
      timeOut = 0;
    }

    delay(1);
  }
  // Returning the file
  return file;
}

void getFileSize(char *fileName) {
  OpenLog.print("size ");
  OpenLog.print(fileName);
  OpenLog.write(13); //This is \r

  //The OpenLog echos the commands we send it by default so we have 'read log823.txt\r' sitting
  //in the RX buffer. Let's try to not print this.
  while(1) {
    if(OpenLog.available())
      if(OpenLog.read() == '\r') break;
  }
}

bool sdIsAvailable() {
  // Code to indicate SD presence. Default to true for now.
  // TODO: Figuire out how to recognize SD presence
  return true
}

//This function pushes OpenLog into command mode
void gotoCommandMode(void) {
  //Send three control z to enter OpenLog command mode
  //Works with Arduino v1.0
  OpenLog.write(26);
  OpenLog.write(26);
  OpenLog.write(26);

  //Wait for OpenLog to respond with '>' to indicate we are in command mode
  while(1) {
    if(OpenLog.available())
      if(OpenLog.read() == '>') break;
  }
}


void checkCardForUpdates() {
  if (!sdIsAvailable()) {
    Serial.println("no card");
    return;
  }

  gotoCommandMode(); //Puts OpenLog in command mode
  updateFromFile(fileName)
}

void updateFromFile() {
  if (getFileSize(firmware_file_name) > 0) {
    Serial.printlnf("file does not exist", result);
    return;
  } else {
    size_t fileSize = getFileSize(firmware_file_name);
  }

  Serial.printlnf("has an image file length=%lu", fileSize);

  FileTransfer::Descriptor file;

  file.file_length = fileSize;
  file.file_address = 0; // Automatically set to HAL_OTA_FlashAddress if store is FIRMWARE
  file.chunk_address = 0;
  file.chunk_size = 100; // default is 0. Changed to match string size in readFile, above
  file.store = FileTransfer::Store::FIRMWARE;


  int result = Spark_Prepare_For_Firmware_Update(file, 0, NULL);
  if (result != 0) {
    Serial.printlnf("prepare failed %d", result);
    return;
  }

  Serial.printlnf("chunk_size=%d file_address=0x%x", file.chunk_size, file.file_address);

  // Removed buff
  // Typically 512 bytes
  // uint8_t *buf = (uint8_t *) malloc(file.chunk_size);
  // if (result != 0) {
  //   Serial.println("failed to allocate buffer");
  //   return;
  // }

  // Note that Spark_Prepare_For_Firmware_Update sets file.file_address so it's not really zero here
  // even though it's what we initialize it to above!
  file.chunk_address = file.file_address;

  // Save file chars
  char* tempFile = readFile(firmware_file_name);

  size_t offset = 0;
  bool succeeded = true;

  while(offset < fileSize) {
    if (file.chunk_size > (fileSize - offset)) {
      file.chunk_size = (fileSize - offset);
    }


    // consider having an error for failed file read??
    // if () {
    //   Serial.println("read failed");
    //   succeeded = false;
    //   break;
    // }

    Serial.printlnf("chunk_address=0x%x chunk_size=%d", file.chunk_address, file.chunk_size);
    result = Spark_Save_Firmware_Chunk(file, (uint8_t*)tempFile[offset], NULL);
    if (result != 0) {
      Serial.printlnf("save chunk failed %d", result);
      succeeded = false;
      break;
    }

    file.chunk_address += file.chunk_size;
    offset += file.chunk_size;
  }

  // free(buf);

  result = Spark_Finish_Firmware_Update(file, succeeded, NULL);
  if (result != 0) {
    Serial.printlnf("finish failed %d", result);
    return;
  }

  if (succeeded) {
    loadStage++;
  }

  Serial.printlnf("update complete");
}

When you want to append a string (char[] or char*) to another given string then you'd use strcat() (string concatenation). But since you are dealing with binary data you can have '\0' chracters in your "strings" which will interfer with any of the strxxx() functions.

Also with a char* you are only defining a pointer to a single character but don't reserve any memory to store that (let alone anymore than one) anywhere.
That's why the original idea of using char tempString[100] was the better choice.

In Spark_Save_Firmware_Chunk() file is not only a char* but an object ( FileTransfer::Descriptor file;)

So would it make sense to just return tempString as the complete file contents? Would I need to make the length of the string the file length to allow the complete file to be saved to to the char?, like tempString[fileLength]

So what I was trying to do/my understanding is that what I had referred to as file in the readFile() function is a char* and becomes (uint8_t*)tempFile[offset] in the Spark_Save_Firmware_Chunk parameters. Also, if I followed your advice above, tempFile would be what is returned in readFile, a char[fileLength]

What I referred to as file in updateFromFile() is a an object (FileTransfer::Descriptor file;) So something like this:

result = Spark_Save_Firmware_Chunk(file, (uint8_t*)tempString[offset], NULL);

ps thanks for putting up with my pretty basic questions!

Your array should be at least of chunk_size. That chunk_size is fixed by the demands of Spark_Save_Firmware_Chunk() - see Rick’s original code.

And you have to let go of the notion that you are dealing with strings - it’s a chunk of binary data which would break a string at any ' \0' byte.