• 0 Posts
  • 3 Comments
Joined 2 years ago
cake
Cake day: September 8th, 2023

help-circle
  • I don’t think the promise chain is really needed here.

    I used this script:

    import Axios from 'axios'
    import OldFS from 'fs'
    import { PromiseChain } from '@feather-ink/ts-utils'
    
    const fs = OldFS.promises
    
    const image = process.argv[2]
    const destination = `http://${process.argv[3]}/vfs/ota`
    const now = process.argv[4] === 'now'
    const once = process.argv[4] === 'once'
    
    async function triggerUpdate(): Promise<void> {
      console.log('Uploading new binary')
      const file = await fs.readFile(image)
    
      await Axios({
        method: 'POST',
        url: destination,
        headers: {
          'Content-Type': 'application/octet-stream',
          'Content-Length': file.byteLength
        },
        data: file
      })
      console.log('Finished uploading')
    }
    
    (async () => {
      const updateChain = new PromiseChain()
      console.log(`Watching file '${image}' for changes\nWill upload to '${destination}'!`)
      if (once) {
        await triggerUpdate()
        return
      }
      if (now)
        await updateChain.enqueue(triggerUpdate)
      OldFS.watch(image, async (eventType) => {
        if (eventType !== 'change')
          return
        let succ = false
        do {
          try {
            console.log('Change detected')
            await updateChain.enqueue(triggerUpdate)
            succ = true
          } catch (e) {
            console.error(e)
            console.log('Retrying upload')
          }
        } while (!succ)
        console.log('Upload finished')
      })
    })()
    

    Relevent code on the esp:

    You can ignore my cpp stuff and just put this in the handler of the stock webserver.

    auto ota = vfs->addHandler(makeDirectory("ota"));
            {
              ota->addHandler(makeDirect([](auto &con) {
                if (con.req->method != HTTP_POST)
                  return HandlerReturn::UNHANDLED;
    
                // https://github.com/espressif/esp-idf/tree/master/examples/system/ota/native_ota_example/main
                // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/ota.html
                auto updatePartition = esp_ota_get_next_update_partition(nullptr);
                if (updatePartition == nullptr)
                  return sendError(con,500, "No free ota partition found!");
                esp_ota_handle_t otaHandle;
                auto err = esp_ota_begin(updatePartition, con.req->content_len, &otaHandle);
                if (err != ESP_OK)
                  return sendError(con, 500, std::string{"Can't start ota update: "} + esp_err_to_name(err), true);
    
                int receivedBytes = 0;
                do {
                  auto end = httpd_req_recv(con.req, buf.data(), buf.size());
                  // ESP_LOGE(TAG, "Received %d", receivedBytes);
                  // hexDump("RECV:", buf.data(), end);
                  if (end <= 0) {
                    esp_ota_abort(otaHandle);
                    return sendError(con, 500, "Error receiving", true);
                  }
                  err = esp_ota_write(otaHandle, buf.data(), end);
                  if (err != ESP_OK) {
                    esp_ota_abort(otaHandle);
                    return sendError(con, 500, std::string{"Error writing: "} + esp_err_to_name(err), true);
                  }
                  receivedBytes += end;
                } while (receivedBytes < con.req->content_len);
    
                err = esp_ota_end(otaHandle);
                if (err != ESP_OK)
                  return sendError(con, 500, std::string{"Failed to end: "} + esp_err_to_name(err), true);
    
                err = esp_ota_set_boot_partition(updatePartition);
                if (err != ESP_OK)
                  return sendError(con, 500, std::string{"esp_ota_set_boot_partition failed: "} + esp_err_to_name(err), true);
                auto ret = sendOK(con);
                FactoryResetServiceCon().reboot(1000 / portTICK_PERIOD_MS);
                return ret;
              }));
            }
    

    I also used a custom partition table for 2 partitions so that when my program crashes it can just go back to boot the previous version.

    Here it is for reference:

    partitions.csv

    # Name,   Type, SubType, Offset,  Size, Flags
    # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
    nvs,      data, nvs,     0x011000, 0x006000,
    otadata,  data, ota,     0x017000, 0x002000,
    phy_init, data, phy,     0x019000, 0x001000,
    ota_0,    app,  ota_0,   0x020000, 0x1F0000,
    ota_1,    app,  ota_1,   0x210000, 0x1F0000,
    

    Note: This partition table is for a special model of the ESP32 though.

    Also another disclaimer: This code does not represent my current coding abilities and may be outdated - it worked well though.



  • Back in school my friends all flashed their mcus with 4-8MB images over serial with 115200 baud. I set up ota updates over wifi. They were all fascinated by my speedy flashes. However when I offered to help them set it up, not one was interested because their setup was working as is and slow flashing is not a “bad” thing since it gave them an excuse to do other things.

    We are talking minutes vs seconds here.

    The teachers were surprised by my quick progress and iterations. When I told them my “trick” the gave me bonus points but also were not interested in learning how to do ota which was very easy. A simple 20 minute first time setup would have saved sooo much time during the year.