Skip to content

Commit

Permalink
Merge pull request #6 from mcci-catena/issue5
Browse files Browse the repository at this point in the history
fix #5: add feature to load FW via Serial
  • Loading branch information
terrillmoore authored Jun 30, 2023
2 parents b4bfa08 + c0d54f8 commit 63a34da
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 2 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ This sketch is the production firmware for the MCCI® [Model 4841 Air Quality

- [Introduction](#introduction)
- [Installing and Building](#installing-and-building)
- [Firmware Update via Serial](#firmware-update-via-serial)
- [Steps to update a Firmware via Serial](#steps-to-update-a-firmware-via-serial)
- [Overview](#overview)
- [Activities](#activities)
- [Primary Data Acquisition](#primary-data-acquisition)
Expand Down Expand Up @@ -63,6 +65,40 @@ The Model 4841 combines an MCCI Catena® 4630 LPWAN radio sensor board with a

The best way to install and build this software is to start with [COLLECTION-model4841](https://github.com/mcci-catena/COLLECTION-model4841), a top-level collection that includes this repository as a submodule, along with the required libraries and a build procedure that uses the [`arduino-cli`](https://arduino.github.io/arduino-cli/). It is, of course, also possible to install the libraries individually and build with a variety of build procedures. See "[Required libraries](#required-libraries)" for details.

## Firmware Update via Serial

This is a feature to load the firmware via serial without getting the board into DFU mode.
Version 1.4.0 and later version of the sketch supports this feature.

### Steps to update a Firmware via Serial

- As the feature is available from version 1.4.0 and later of the sketch, the existing firmware on board should be the version v1.4.0 or later version firmware.
- Clone the repository in a desired location.
- Place the bin file of the sketch inside "extra" folder of the repository.
- Open the cmd prompt and navigate to "extra" folder where the bin file was placed.
- Run the following example command:

```py
python catena-download.py -v -c "system update" model4841-production-lorawan.ino.bin COM10
```
- This will load the new firmware in Catena board with a "Success!" message.

```
D:\IoT-Projects\Model4841\sketches\model4841-production-lorawan\extra>python catena-download.py -v -c "system update" model4841-production-lorawan.ino.bin COM10
Read file: size 104108
Using port COM10
system update
Update firmware: echo off, timeout 2 seconds
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<OK<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Success!
```
- Once the firmware is loaded, RESET Model 4841, this will start to run the newly loaded sketch in Model 4841.

**Note**
- COM port (COM10 used in example command) might vary for different devices. Use the COM port which is enumerated for the Catena board in use.
- To find the COM port, open the "device manager" from start menu and find the COM port which is enumerated for the Catena board in use.
- Make sure the COM port is not connected to any Serial monitors.

## Overview

The primary function of the Catena 4630 is to capture and transmit real-time air quality and dust particle data to remote data consumers, using LoRaAWAN. For consistency with MCCI's other monitoring products, information is captured and transmitted at six minute intervals.
Expand Down Expand Up @@ -308,6 +344,16 @@ The following additional libraries are used in this project.

Note that the libraries mentioned here are actually govenered by the versions chosen in [COLLECTION-model4841](https://github.com/mcci-catena/COLLECTION-model4841).

v1.4.0 has the following changes.

- Updated the production sketch with a new feature "Firmware Update via Serial" to avoid getting the board into DFU mode.
- Use the following library:

| Name | Version | Comments
|------|:-------:|:-----------
| BSP | 3.1.0 | Pick up latest release
| Catena-Arduino-Platform | 0.21.2 | Pick up bug fixes

v1.3.2 only has changes in the build system.

| Name | Version | Comments
Expand Down
128 changes: 128 additions & 0 deletions extra/catena-download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3

##############################################################################
#
# Module: catena-download.py
#
# Function:
# Push a file to Catena
#
# Copyright and License:
# Copyright (C) 2021, MCCI Corporation. See accompanying LICENSE file
#
# Author:
# Terry Moore, MCCI April, 2021
#
##############################################################################

import argparse
import serial

global gVerbose
gVerbose = False

##############################################################################
#
# Name: ParseCommandArgs()
#
# Function:
# Parse the command line arguments
#
# Definition:
# ParseCommandArgs() -> Namespace
#
##############################################################################

def ParseCommandArgs():
def checkPath(s):
result = Path(s)
if not result.is_dir():
raise ValueError("not a directory")

return result

parser = argparse.ArgumentParser(description="download an image file to a Catena using the protocol demonstrated by catena-download.ino")
parser.add_argument(
"hInput",
metavar="{inputBinFile}",
type=argparse.FileType('rb'),
help="Input binary file"
)
parser.add_argument(
"sComPort", metavar="{portname}",
help="Where to put generated output files"
)
parser.add_argument(
"--baud", "-b",
help="baud rate",
default=115200
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="verbose output",
default=False
)
parser.add_argument(
"--command", "-c", metavar="{command}",
help="Command to send to trigger update",
default="system update",
dest="sCommand"
)
return parser.parse_args()

# main function
def main():
global gVerbose
args = ParseCommandArgs()
gVerbose = args.verbose

# get the command as a buffer
# read the file
fileImage = args.hInput.read(-1)
args.hInput.close()
if gVerbose:
print("Read file: size {}".format(len(fileImage)))
v = memoryview(fileImage)

# open the serial port
hPort = serial.Serial(args.sComPort, baudrate=args.baud)
if gVerbose:
print("Using port {}".format(hPort.name))

# read and discare chars
hPort.read(hPort.in_waiting);

# send the command
hPort.write(args.sCommand.encode('ascii') + b"\n");

i = 0
chunkSize = 128
zpad = b'\0' * chunkSize

while True:
c = hPort.read(1)
if gVerbose:
print(c.decode(encoding="ascii"), end='', sep='', flush=True)
if c == b'?':
msg = hPort.read_until()
if gVerbose:
print(msg.decode(encoding="ascii"), end='', sep='', flush=True)
print("Failed!")
exit(1);
if c == b'<':
chunk = fileImage[i: i + chunkSize]
if len(chunk) != 0:
i += len(chunk)
chunk = (chunk + zpad)[0 : chunkSize]
hPort.write(chunk)
elif c == b'O':
msg = hPort.read_until()
if gVerbose:
print(msg.decode(encoding="ascii"), end='', sep='', flush=True)
print("Success!")
exit(0)

#### the standard trailer #####
if __name__ == '__main__':
main()
138 changes: 136 additions & 2 deletions model4841-production-lorawan.ino
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Author:
#include <sgpc3.h>
#include <mcciadk_baselib.h>
#include <stdlib.h>
#include <Catena_Download.h>
#include <Catena_BootloaderApi.h>

#include <cstdint>

Expand All @@ -49,7 +51,7 @@ using namespace McciCatenaSht3x;
|
\****************************************************************************/

constexpr std::uint32_t kAppVersion = McciCatenaPMS7003::makeVersion(1,3,2,0);
constexpr std::uint32_t kAppVersion = McciCatenaPMS7003::makeVersion(1,4,0,0);

// make sure we pick up library with fixes for 59-day problem.
static_assert(CATENA_ARDUINO_PLATFORM_VERSION >= CATENA_ARDUINO_PLATFORM_VERSION_CALC(0,20,0,30));
Expand Down Expand Up @@ -82,6 +84,15 @@ SPIClass gSPI2(
Catena_Mx25v8035f gFlash;
bool gfFlash;

/* instantiate a serial object */
cSerial<decltype(Serial)> gSerial(Serial);

/* instantiate the bootloader API */
cBootloaderApi gBootloaderApi;

/* instantiate the downloader */
cDownload gDownload;

// The Temperature/humidity sensor
cSHT3x gTempRh { Wire };

Expand Down Expand Up @@ -132,6 +143,26 @@ sMyExtraCommands_top(
nullptr /* this is no "first word" for all the commands in this table */
);

// forward reference to the command function
static cCommandStream::CommandFn cmdUpdate;

// the individual commmands are put in this table
static const cCommandStream::cEntry sMyFWUpdateCommmands[] =
{
{ "fallback", cmdUpdate },
{ "update", cmdUpdate },
// other commands go here....
};

/* a top-level structure wraps the above and connects to the system table */
/* it optionally includes a "first word" so you can for sure avoid name clashes */
static cCommandStream::cDispatch
sMyFWUpdateCommmands_top(
sMyFWUpdateCommmands, /* this is the pointer to the table */
sizeof(sMyFWUpdateCommmands), /* this is the size of the table */
"system" /* this is the "first word" for all the commands in this table*/
);

/****************************************************************************\
|
| Setup
Expand All @@ -145,9 +176,9 @@ void setup()

setup_flash();
setup_radio();
setup_commands();
setup_sensors();
setup_pms7003();
setup_commands();
setup_measurement();
}

Expand Down Expand Up @@ -304,6 +335,109 @@ void setup_commands()
*/
nullptr
);
/* add our application-specific commands */
gCatena.addCommands(
/* name of app dispatch table, passed by reference */
sMyFWUpdateCommmands_top,
/*
|| optionally a context pointer using static_cast<void *>().
|| normally only libraries (needing to be reentrant) need
|| to use the context pointer.
*/
nullptr
);

gDownload.begin(gFlash, gBootloaderApi);
}

/* process "system" "update" / "system" "fallback" -- args are ignored */
// argv[0] is "update" or "fallback"
// argv[1..argc-1] are the (ignored) arguments
static cCommandStream::CommandStatus cmdUpdate(
cCommandStream *pThis,
void *pContext,
int argc,
char **argv
)
{
cCommandStream::CommandStatus result;

pThis->printf(
"Update firmware: echo off, timeout %d seconds\n",
(cDownload::kTransferTimeoutMs + 500) / 1000
);

if (! gfFlash)
{
pThis->printf(
"** flash not found at init time, can't update **\n"
);
return cCommandStream::CommandStatus::kIoError;
}

gSPI2.begin();
gFlash.begin(&gSPI2, Catena::PIN_SPI2_FLASH_SS);

struct context_t
{
cCommandStream *pThis;
bool fWorking;
cDownload::Status_t status;
cCommandStream::CommandStatus cmdStatus;
cDownload::Request_t request;
};

context_t context { pThis, true };

auto doneFn =
[](void *pUserData, cDownload::Status_t status) -> void
{
context_t * const pCtx = (context_t *)pUserData;

cCommandStream * const pThis = pCtx->pThis;
cCommandStream::CommandStatus cmdStatus;

cmdStatus = cCommandStream::CommandStatus::kSuccess;

if (status != cDownload::Status_t::kSuccessful)
{
pThis->printf(
"download error, status %u\n",
unsigned(status)
);
cmdStatus = cCommandStream::CommandStatus::kIoError;
}

pCtx->cmdStatus = cmdStatus;
pCtx->fWorking = false;
};

if (gDownload.evStartSerialDownload(
argv[0][0] == 'u' ? cDownload::DownloadRq_t::GetUpdate
: cDownload::DownloadRq_t::GetFallback,
gSerial,
context.request,
doneFn,
(void *) &context)
)
{
while (context.fWorking)
gCatena.poll();

result = context.cmdStatus;
}
else
{
pThis->printf(
"download launch failure\n"
);
result = cCommandStream::CommandStatus::kInternalError;
}

gFlash.powerDown();
gSPI2.end();

return result;
}

void setup_measurement()
Expand Down

0 comments on commit 63a34da

Please sign in to comment.