Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • diagActivate - (1)

  • diagPoll - (4, 6, 16, 2019, 3122, 38 32)

  • diagResponse - (3538)

  • diagPoll - (3841, 49 52, 53 56, 65, 75)

  • diagResponse - (6981)

  • diagPoll - (7284)

Code Block
languagenone
diag: Entered application callback function: diagActivate
	appId: 3
diagnostic activated with 600s activation period and 5s poll interval
diag: Entered application callback function: diagPoll
	appId: 3	state: ONCE
diag: Entered application callback function: diagPoll
	appId: 3	state: ACTIVATED
diag: template registration request
240036 note.template
diagnostic now SENDING_REQUEST
240036 to gateway:54 sensor sending request (152159)
240036 to gateway:54 sending ( (152159/152159) at txp:-17
240036 to gateway:54 waiting 2s9s to transmit (slot 0s-20s in 20s window)
diagnostic state will be set to 1 on success, or 0 on error
sched: sleeping 3s4s (next transmit window in 1s8s)
diag: Entered application callback function: diagPoll
	appId: 3	state: SENDING_REQUEST
sched: sleeping 5s4s 240036(next totransmit gateway:5window waiting for message from gateway
in 3s)
diag: Entered application callback function: diagPoll
	appId: 3	state: SENDING_REQUEST
sched: sleeping 4s
(nextdiag: transmitEntered windowapplication incallback 12s)function: 240036 fm gateway:5diagPoll
	appId: 3	state: SENDING_REQUEST
sched: sleeping 5s (next transmit window in 14s)
240036 to gateway:4 waiting for message from gateway
240036 fm gateway:4 ack received
ATP: buffered sample 21
ATP: rssi/snr:-3433/76 txp:-17
ATP: | [31] - - - - - - - - - - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - 
diagnostic now RECEIVING_RESPONSE (waiting for response)
240036 fm gateway:54 waiting for response from gateway
240036diag: fmEntered gateway:5application nowcallback receivingfunction: response diagPoll
	appId: 3	state: RECEIVING_RESPONSE
sched: sleeping 4s (next transmit window in 7s)
240036 fm gateway:4 now receiving response from gateway
240036 fm gateway:54 sending ACK (0/0) at txp:-17
diagnostic now 1 (response completed)
diag: Entered application callback function: diagPolldiagResponse
	appId: 3	statersp: RECEIVING_RESPONSE
sched: sleeping 4s (next transmit window in 8s)
diagnostic now 1 (response completed)
diag: Entered application callback function: diagResponse
	appId: 3	rsp: {"id":2,"{"id":19790917,"bytes":32}
diag: SUCCESSFUL template registration
diag: Entered application callback function: diagPoll
	appId: 3	state: STATE_DIAG_CHECK
diag: generating diagnostic report
240036 note.add
diagnostic now SENDING_REQUEST
240036 to gateway:65 sensor sending request (159172)
240036 to gateway:65 sending ( (159170/159172) at txp:-17
240036 to gateway:65 waiting 3s to transmit (slot 0s-20s in 20s window)
diagnosticdiag: statenote will request sent
diagnostic state will be set to 1 on success, or 0 on error
diag: note request sent
sched: sleeping 4s (next transmit window in 3s2s)
diag: Entered application callback function: diagPoll
	appId: 3	state: SENDING_REQUEST
sched: sleeping 4s (next transmit window in 17s)
240036 to gateway:65 waiting for message from gateway
diag: Entered application callback function: diagPoll
	appId: 3	state: SENDING_REQUEST
sched: sleeping 4s5s (next transmit window in 13s)
240036 fm gateway:65 ack received
ATP: buffered sample 32
ATP: would decrease power but it's already bottomed-out at -17db
ATP: rssi/snr:-44/5 avg:-37/6 7 txp:-17
ATP: | [53] - - - - - - - - - - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - 
diagnostic240036 now RECEIVING_RESPONSEfm gateway:5 sending (waiting for response (2/172) 240036at fm gateway:6 waiting for response from gatewaytxp:-17
240036 fmto gateway:65 nowwaiting receivingfor responsemessage from gateway
240036 fm gateway:6 sending ACK
(0/0) at txp:-17
diag: Entered application callback function: diagPoll
	appId: 3	state: RECEIVINGSENDING_RESPONSEREQUEST
sched: sleeping 4s (next transmit window in 8s7s)
diagnostic240036 now 1 (response completed)
diag: Entered application callback function: diagResponse
	appId: 3	rsp: {"template":true}
diag: SUCCESSFUL Note submission
diag: Entered application callback function: diagPoll
	appId: 3	state: STATE_DIAG_CHECK
diagnostic now DEACTIVATED (diag: completed)
diagnostic deactivated

Implementation Details

Defines

Custom Application States

Code Block
languagec
#define STATE_DIAG_ABORT       0
#define STATE_DIAG_CHECK       1
#define STATE_DIAG_ISR_XFER    2
  • STATE_DIAG_ABORT - This state is reserved for application specific error conditions, and will allow for graceful deactivation of the application.

  • STATE_DIAG_CHECK - When this state is received by the polling callback and the application has not be marked for deactivation, it will generate a diagnostic report, send the results to the Gateway Notecard, and mark itself ready for deactivation. Otherwise, if this state is received by the polling callback and the application has already been marked for deactivation then it will log a completion message and immediately deactivate.

  • STATE_DIAG_ISR_XFER - This state is set from within the ISR, and it allows the polling callback to know the interrupt was invoked.

Application Specific Values

Code Block
languagec
#define REQUESTID_TEMPLATE     19790917
  • REQUESTID_TEMPLATE - An arbitrary number allowing you to match a response and request. In order to keep track of a response to specific Note, you need to supply an “id” tag to the Note. When an “id” is supplied to a Note, then the response parameter provided to diagResponse() will contain the matching “id”.

Code Block
languagec
#define APPLICATION_NOTEFILE "*#diag.qo"
  • APPLICATION_NOTEFILE - The dynamic filename of the application specific queue.

    NOTE: The Gateway will replace * with the originating node's ID. The resulting transformation will resemble 012345678901234567890123#diag.qo.

Array and Counter Limits

Code Block
languagec
#define ISR_MAX_CALL_RETENTION 8  // Must be a power of 2
#define ISR_COUNTER_MASK       ~(ISR_MAX_CALL_RETENTION-1)
  • ISR_MAX_CALL_RETENTION - The maximum number of ISRs that will be recorded.

  • ISR_COUNTER_MASK - The mask will limit the value of the counter.

Structures

  • isrParameters - Since an APP_PRINT() statement cannot be used from within an interrupt service routine, this structure is used to collect the calling parameters of the ISR for later processing.

    Code Block
    typedef struct isrParameters {
        int appID;
        uint16_t pins;
    } isrParameters;
    • appID - The application ID passed to the ISR callback

    • pins - The pin(s) prompting the ISR invocation.

  • applicationContext - A rather trivial example of application context, but never the less ALL the application state is stored in a single structure which is shared between callbacks.

    Code Block
    typedef struct applicationContext {
        // ISR call ring-buffer
        volatile size_t isrCount;
        volatile bool isrOverflow;
        isrParameters isrParams[ISR_MAX_CALL_RETENTION];
    
        // Application status
        bool templateRegistered;  // Only `true` once we've successfully registered the template
        bool done;
    } applicationContext;
    • isrCount - The number of times the ISR was invoked.

    • isrOverflow - Indicates whether the ISR was invoked more than ISR_MAX_CALL_RETENTION times.

    • isrParams - An array dedicated to recording the calling parameters of each ISR invocation.

    • templateRegistered - Provides a mechanism for the polling callback to only attempt to register a template once.

    • done - Provides a mechanism for the polling callback to indicate when a note has been submitted to the Notecard, so it will know when to deactivate itself.

Global Static Variables (a.k.a. Singleton Application Specific Context)

none

Callbacks

...

bool diagActivate(int appID, void *appContext)

Code Block
languagec
// Load Application Context
applicationContext *ctx = appContext;

This line casts the context, so it can be used in subsequent operations.

Code Block
languagec
APP_PRINTF("diag: Entered application callback function: diagActivate\r\n\tappId: %d\r\n", appID);

This line logs the entry into the function along with the calling parameters.

Code Block
languagec
ctx->done = false;

The function resets the done state, so the application will progress until it has generated a Note or has failed during execution.

Code Block
languagec
// Success
return true;

The function returns true, so the application will progress to the polling callback

void diagISR(int appID, uint16_t pins, void *appContext);

Code Block
languagec
// Load Application Context
applicationContext *ctx = appContext;

This line casts the context, so it can be used in subsequent operations.

Code Block
languagec
/*
 * This callback function is executed directly from the ISR.
 * Only perform ISR sensitive operations and exit quickly.
 */
ctx->isrParams[ctx->isrCount].appID = appID;
ctx->isrParams[ctx->isrCount].pins = pins;
ctx->isrCount++;
ctx->isrCount = (ISR_COUNTER_MASK & ctx->isrCount);
ctx->isrOverflow = (ctx->isrOverflow || !ctx->isrCount);

The function executes inside an actual ISR, so there is no time to perform expensive operations (like logging). Therefore, it collects the calling parameters and updates metadata. This is necessary to track how many times the interrupt has been called, so it may log the details outside the ISR.

Code Block
languagec
if ((pins & BUTTON1_Pin) && !schedIsActive(appID)) {
    schedActivateNowFromISR(appID, true, STATE_DIAG_ISR_XFER);
}

...

Code Block
diag: ISR callback function called  <5> times.
diag: call 0:	appId: 3	pins: 128
diag: call 1:	appId: 3	pins: 128
diag: call 2:	appId: 3	pins: 8192
diag: call 3:	appId: 3	pins: 128
diag: call 4:	appId: 3	pins: 128

Since the parameter collection is performed outside the filter, the ISR callback is invoked multiple times. However, an ISR is only significant to this application when pin equals 8192 (which is 0x2000 or BUTTON1_Pin). As you can see, there are other invocations where pin equals 128. Those invocations are related to the PIR application interrupts, and are ignored by this application.

  • void diagPoll(int appID, int state, void *appContext);

    Code Block
    // Load Application Context
    applicationContext *ctx = appContext;

    This line casts the context, so it can be used in subsequent operations.

    Code Block
    APP_PRINTF("diag: Entered application callback function: diagPoll\r\n\tappId: %d\tstate: %s\r\n", appID, diagStateName(state));

    This line logs the entry into the function along with the calling parameters.

    Code Block
    languagec
    case STATE_ACTIVATED:
        if (!ctx->templateRegistered) {
            registerNotefileTemplate();
            schedSetCompletionState(appID, STATE_DIAG_CHECK, STATE_DIAG_ABORT);
        } else {
            schedSetState(appID, STATE_DIAG_CHECK, "diag: process diagnostics");
        }
        break;

    This case handles the newly activated state. It will first check to see if a template has been registered in a previous invocation of the polling callback.
    If a template has not been registered, then it will attempt to do so. If the request is delivered successfully, then the state will be changed to the next state in the progression, to process the diagnostic values. Otherwise, if the message fails to be delivered, then the state will be set to the abort state.
    If a template has already been registered on a previous invocation, then the state will be changed to the next state, to process diagnostic values.

    Code Block
    languagec
    case STATE_DIAG_ISR_XFER:
        APP_PRINTF("diag: Transfered from application ISR callback function.\r\n");
        APP_PRINTF("diag: ISR callback function called %s <%d> times.\r\n", (ctx->isrOverflow ? "more than" : ""), (ctx->isrOverflow ? ISR_MAX_CALL_RETENTION : ctx->isrCount));
        if (ctx->isrOverflow) { ctx->isrCount = ISR_MAX_CALL_RETENTION; }
        for (size_t i = 0 ; i < ctx->isrCount ; ++i) {
            APP_PRINTF("diag: call %d:\tappId: %d\tpins: %d\r\n", i, ctx->isrParams[i].appID, ctx->isrParams[i].pins);
        }
        resetIsrValues(ctx);
        // fall through and report diagnostics

    This case handles a transfer from the interrupt routine. It will print the invocation, along with the parameters, of of each recorded ISR, up to ISR_MAX_CALL_RETENTION times. Once it has exhausted it’s cache, it will reset all values related to the ISR. Instead of exiting, it will fall through into the next case…

    Code Block
    case STATE_DIAG_CHECK:
        if (ctx->done) {
            schedSetState(appID, STATE_DEACTIVATED, "diag: completed successfully");
            break;
        }
        schedSetCompletionState(appID, STATE_DIAG_CHECK, STATE_DIAG_ABORT);
        addNote(true);
        break;

    This case handles the main objective of the application, the collection of diagnostics and sending a Note to the Notehub. It will check for the done flag, which is only set once a Note has been successfully received by the Notecard. If done is set, then the application will log a success message and request to be deactivated. If the done flag has not been set, then schedSetCompletionState is called to instruct the scheduler what to do when the request to add a Note either succeeds or fails. As you can see above, if the request is successful, then it will return to this state, but the done flag will be true, and the app will request deactivation. However, if it were to fail, then the application to move on to the abort state. Finally, the request to add a Note is made.

    Code Block
    case STATE_DIAG_ABORT:
        schedSetState(appID, STATE_DEACTIVATED, "diag: aborted due to failure!");
        break;

    This case is reserved for fail cases encountered during the application processing. It request for the application to be deactivated and generates a log indicating the failure.

  • void diagResponse(int appID, J *rsp, void *appContext);

Other Functions

External

  • bool diagInit(void);

Internal

...

static void addNote(bool immediate);

...

static const char * diagStateName (int state);

Code Block
switch (state) {
case STATE_DIAG_ABORT:
    return "STATE_DIAG_ABORT";
case STATE_DIAG_CHECK:
    return "STATE_DIAG_CHECK";
case STATE_DIAG_ISR_XFER:
    return "STATE_DIAG_ISR_XFER";
default:
{
    static char undefined_state[20];
    schedStateName(state, undefined_state, sizeof(undefined_state));
    return undefined_state;
}
}

The function translates the int value of an application specific state into a const char * description of the state. In the default case, where a state is unrecognized, it will defer to the analogous system API, which will either transcribe the state or simply print the integer's string representation.

...

fm gateway:5 ack received
ATP: buffered sample 3
ATP: would decrease power but it's already bottomed-out at -17db
ATP: rssi/snr:-44/7 avg:-40/6 txp:-17
ATP: | [4] - - - - - - - - - - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - 
diagnostic now RECEIVING_RESPONSE (waiting for response)
240036 fm gateway:5 waiting for response from gateway
diag: Entered application callback function: diagPoll
	appId: 3	state: RECEIVING_RESPONSE
sched: sleeping 5s (next transmit window in 3s)
240036 fm gateway:5 now receiving response from gateway
240036 fm gateway:5 sending ACK (0/0) at txp:-17
diagnostic now 1 (response completed)
diag: Entered application callback function: diagResponse
	appId: 3	rsp: {"id":9171979,"template":true}
diag: SUCCESSFUL Note submission
diag: Entered application callback function: diagPoll
	appId: 3	state: DIAG_CHECK
diagnostic now DEACTIVATED (diag: completed successfully)
diagnostic deactivated

Implementation Details

Source Code Link: https://github.com/blues/sparrow-reference-firmware/blob/main/sparrow-application/diag/diag.c

Defines

Custom Application States

Code Block
languagec
#define STATE_DIAG_ABORT       0
#define STATE_DIAG_CHECK       1
#define STATE_DIAG_ISR_XFER    2
  • STATE_DIAG_ABORT - This state is reserved for application specific error conditions, and will allow for graceful deactivation of the application.

  • STATE_DIAG_CHECK - When this state is received by the polling callback and the application has not be marked for deactivation, it will generate a diagnostic report, send the results to the Gateway Notecard, and mark itself ready for deactivation. Otherwise, if this state is received by the polling callback and the application has already been marked for deactivation then it will log a completion message and immediately deactivate.

  • STATE_DIAG_ISR_XFER - This state is set from within the ISR, and it allows the polling callback to know the interrupt was invoked.

Application Specific Values

Code Block
languagec
#define REQUESTID_DIAGNOTE     9171979
#define REQUESTID_TEMPLATE     19790917
  • REQUESTID_DIAGNOTE - An arbitrary number to match a diagnostic Note response and request.

  • REQUESTID_TEMPLATE - An arbitrary number to match the template response and request.

Code Block
languagec
#define APPLICATION_NOTEFILE "*#diag.qo"
  • APPLICATION_NOTEFILE - The dynamic filename of the application specific queue.

    NOTE: The Gateway will replace * with the originating node's ID. The resulting transformation will resemble 012345678901234567890123#diag.qo.

Array and Counter Limits

Code Block
languagec
#define ISR_MAX_CALL_RETENTION 8  // Must be a power of 2
#define ISR_COUNTER_MASK       ~(ISR_MAX_CALL_RETENTION-1)
  • ISR_MAX_CALL_RETENTION - The maximum number of ISRs that will be recorded.

  • ISR_COUNTER_MASK - The mask will limit the value of the counter.

Structures

  • isrParameters - Since an APP_PRINT() statement cannot be used from within an interrupt service routine, this structure is used to collect the calling parameters of the ISR for later processing.

    Code Block
    languagec
    typedef struct isrParameters {
        int appID;
        uint16_t pins;
    } isrParameters;
    • appID - The application ID passed to the ISR callback

    • pins - The pin(s) prompting the ISR invocation.

  • applicationContext - A rather trivial example of application context, but never the less ALL the application state is stored in a single structure which is shared between callbacks.

    Code Block
    languagec
    typedef struct applicationContext {
        // ISR call ring-buffer
        volatile size_t isrCount;
        volatile bool isrOverflow;
        isrParameters isrParams[ISR_MAX_CALL_RETENTION];
    
        // Application status
        bool templateRegistered;  // Only `true` once we've successfully registered the template
        bool done;
    } applicationContext;
    • isrCount - The number of times the ISR was invoked.

    • isrOverflow - Indicates whether the ISR was invoked more than ISR_MAX_CALL_RETENTION times.

    • isrParams - An array dedicated to recording the calling parameters of each ISR invocation.

    • templateRegistered - Provides a mechanism for the polling callback to only attempt to register a template once.

    • done - Provides a mechanism for the polling callback to indicate when a note has been submitted to the Notecard, so it will know when to deactivate itself.

Global Static Variables (a.k.a. Singleton Application Specific Context)

none

Callbacks

  • bool diagActivate(int appID, void *appContext)

    Code Block
    languagec
    // Load Application Context
    applicationContext *ctx = appContext;

    This line casts the context, so it can be used in subsequent operations.

    Code Block
    languagec
    APP_PRINTF("diag: Entered application callback function: diagActivate\r\n\tappId: %d\r\n", appID);

    This line logs the entry into the function along with the calling parameters.

    Code Block
    languagec
    ctx->done = false;

    The function resets the done state, so the application will progress until it has generated a Note or has failed during execution.

    Code Block
    languagec
    // Success
    return true;

    The function returns true, so the application will progress to the polling callback

  • void diagISR(int appID, uint16_t pins, void *appContext);

    Code Block
    languagec
    // Load Application Context
    applicationContext *ctx = appContext;

    This line casts the context, so it can be used in subsequent operations.

    Code Block
    languagec
    /*
     * This callback function is executed directly from the ISR.
     * Only perform ISR sensitive operations and exit quickly.
     */
    ctx->isrParams[ctx->isrCount].appID = appID;
    ctx->isrParams[ctx->isrCount].pins = pins;
    ctx->isrCount++;
    ctx->isrCount = (ISR_COUNTER_MASK & ctx->isrCount);
    ctx->isrOverflow = (ctx->isrOverflow || !ctx->isrCount);

    The function executes inside an actual ISR, so there is no time to perform expensive operations (like logging). Therefore, it collects the calling parameters and updates metadata. This is necessary to track how many times the interrupt has been called, so it may log the details outside the ISR.

    Code Block
    languagec
    if ((pins & BUTTON1_Pin) && !schedIsActive(appID)) {
        schedActivateNowFromISR(appID, true, STATE_DIAG_ISR_XFER);
    }

    These lines filter to ensure the ISR only responds to the PAIR button (BUTTON1_Pin), and checks with the scheduler that the application is not already in an active state. If these conditions are met, then it will request the scheduler to schedule the application for immediate activation instead of respecting the activation period.

    The logs generated by the ISR look like this:

    Code Block
    languagenone
    diag: ISR callback function called  <5> times.
    diag: call 0:	appId: 3	pins: 128
    diag: call 1:	appId: 3	pins: 128
    diag: call 2:	appId: 3	pins: 8192
    diag: call 3:	appId: 3	pins: 128
    diag: call 4:	appId: 3	pins: 128

    Since the parameter collection is performed outside the filter, the ISR callback is invoked multiple times. However, an ISR is only significant to this application when pin equals 8192 (which is 0x2000 or BUTTON1_Pin). As you can see, there are other invocations where pin equals 128. Those invocations are related to the PIR application interrupts, and are ignored by this application.

  • void diagPoll(int appID, int state, void *appContext);

    Code Block
    languagec
    // Load Application Context
    applicationContext *ctx = appContext;

    This line casts the context, so it can be used in subsequent operations.

    Code Block
    languagec
    APP_PRINTF("diag: Entered application callback function: diagPoll\r\n\tappId: %d\tstate: %s\r\n", appID, diagStateName(state));

    This line logs the entry into the function along with the calling parameters.

    Code Block
    languagec
    case STATE_ACTIVATED:
        if (!ctx->templateRegistered) {
            registerNotefileTemplate();
            schedSetCompletionState(appID, STATE_DIAG_CHECK, STATE_DIAG_ABORT);
        } else {
            schedSetState(appID, STATE_DIAG_CHECK, "diag: process diagnostics");
        }
        break;

    This case handles the newly activated state. It will first check to see if a template has been registered in a previous invocation of the polling callback.
    If a template has not been registered, then it will attempt to do so. If the request is delivered successfully, then the state will be changed to the next state in the progression, to process the diagnostic values. Otherwise, if the message fails to be delivered, then the state will be set to the ABORT state.
    If a template has already been registered on a previous invocation, then the state will be changed to the next state, to process diagnostic values.

    Code Block
    languagec
    case STATE_DIAG_ISR_XFER:
        APP_PRINTF("diag: Transfered from application ISR callback function.\r\n");
        APP_PRINTF("diag: ISR callback function called %s <%d> times.\r\n", (ctx->isrOverflow ? "more than" : ""), (ctx->isrOverflow ? ISR_MAX_CALL_RETENTION : ctx->isrCount));
        if (ctx->isrOverflow) { ctx->isrCount = ISR_MAX_CALL_RETENTION; }
        for (size_t i = 0 ; i < ctx->isrCount ; ++i) {
            APP_PRINTF("diag: call %d:\tappId: %d\tpins: %d\r\n", i, ctx->isrParams[i].appID, ctx->isrParams[i].pins);
        }
        resetIsrValues(ctx);
        // fall through and report diagnostics

    This case handles a transfer from the interrupt routine. It will print the invocation, along with the parameters, of of each recorded ISR, up to ISR_MAX_CALL_RETENTION times. Once it has exhausted it’s cache, it will reset all values related to the ISR. Instead of exiting, it will fall through into the next case…

    Code Block
    languagec
    case STATE_DIAG_CHECK:
        if (ctx->done) {
            schedSetState(appID, STATE_DEACTIVATED, "diag: completed successfully");
            break;
        }
        schedSetCompletionState(appID, STATE_DIAG_CHECK, STATE_DIAG_ABORT);
        addDiagnosticNote(true);
        break;

    This case handles the main objective of the application, the collection of diagnostics and sending a Note to the Notehub. It will check for the done flag, which is only set once a Note has been successfully received by the Notecard. If done is set, then the application will log a success message and request to be deactivated. If the done flag has not been set, then schedSetCompletionState is called to instruct the scheduler what to do when the request to add a Note either succeeds or fails. As you can see above, if the request is successful, then it will return to this state, but the done flag will be true, and the app will request deactivation. However, if it were to fail, then the application will proceed to the ABORT state. Finally, the request to add a Note is made.

    Code Block
    languagec
    case STATE_DIAG_ABORT:
        schedSetState(appID, STATE_DEACTIVATED, "diag: aborted due to failure!");
        break;

    This case is reserved for fail cases encountered during the application processing. It request for the application to be deactivated and generates a log indicating the failure.

  • void diagResponse(int appID, J *rsp, void *appContext);

    Code Block
    languagec
    // Load Application Context
    applicationContext *ctx = appContext;

    This line casts the context, so it can be used in subsequent operations.

    Code Block
    languagec
    APP_PRINTF("diag: Entered application callback function: diagResponse\r\n\tappId: %d", appID);
    char *json_string = JConvertToJSONString(rsp);
    APP_PRINTF("\trsp: %s\r\n", json_string);
    free(json_string);

    These lines log the entry into the function along with the calling parameters. Special accommodation is made for the JSON response. APP_PRINTF is limited to 90 characters, so two calls are required to reliably log the parameters.

    Code Block
    languagec
    // See if there's an error
    char *err = JGetString(rsp, "err");
    if (err[0] != '\0') {
        APP_PRINTF("diag: app error response: %d\r\n", err);
        schedSetState(appID, STATE_DIAG_ABORT, "diag: aborting...");
        return;
    }

    These lines will peek inside the response JSON to see if an error message is present. If so, the error message will be logged and the application will proceed to the ABORT state.

    Code Block
    languagec
    // Flash the LED if this is a response to this specific ping request
    switch (JGetInt(rsp, "id")) {
    case REQUESTID_DIAGNOTE:
        ctx->done = true;
        APP_PRINTF("diag: SUCCESSFUL Note submission\r\n");
        break;
    case REQUESTID_TEMPLATE:
        ctx->templateRegistered = true;
        APP_PRINTF("diag: SUCCESSFUL template registration\r\n");
        break;
    default:
        APP_PRINTF("diag: received unexpected response\r\n");
    }

    This switch statement is designed to handle responses to successful requests. The switch operates on the unique identifiers given to each request. If the ID of a diagnostic Note is encountered, the done flag is set and a success log is generated. If the ID associated with a template request is encountered, then the flag indicating the template was successfully recorded is set and a log is generated. If neither of these IDs are present, then a log is generated indicating the response was not recognized.

Other Functions

External

  • bool diagInit(void);

    Code Block
    languagec
    APP_PRINTF("diag: Initializing application...\r\n");

    This line logs the entry into the function.

    Code Block
    languagec
    bool result = false;

    This line initializes the result of the function to false, which means “Failed to init”.

    Code Block
    languagec
    // Allocate and initialize application context
    applicationContext *ctx = (applicationContext *)malloc(sizeof(applicationContext));
    ctx->templateRegistered = false;
    ctx->done = false;
    resetIsrValues(ctx);

    These lines allocate and initialize the values of the portable application context.

    Code Block
    languagec
    // Register the application
    schedAppConfig config = {
        .name = "diagnostic",
        .activationPeriodSecs = 60 * 10,
        .pollPeriodSecs = 5,
        .activateFn = diagActivate,
        .interruptFn = diagISR,
        .pollFn = diagPoll,
        .responseFn = diagResponse,
        .appContext = ctx,
    };

    These lines initialize the application configuration that will be passed to the scheduler.

    Code Block
    languagec
    if (schedRegisterApp(&config) < 0) {
        // Failure
        result = false;
    } else {
        // Success
        result = true;
    }

    These lines will attempt to register the application with the scheduler. On success, an application identifier greater than or equal to zero is returned, otherwise a negative value is returned to indicate registration failure. For basic usage, it is unnecessary to capture the application identifier, because it is returned as the first parameter of each of the callbacks registered by the application.

    Code Block
    languagec
    return result;

    This line returns the result to the caller.

Internal

  • static void addDiagnosticNote(bool immediate);

    Code Block
    languagec
    APP_PRINTF("diag: generating diagnostic report\r\n");

    This line simply logs the intent of the function.

    Code Block
    languagec
    // Create the request
    J *req = NoteNewRequest("note.add");
    if (req == NULL) {
        return;
    }
    
    // If immediate, sync now
    if (immediate) {
        JAddBoolToObject(req, "sync", true);
    }
    
    // Set the target notefile
    JAddStringToObject(req, "file", APPLICATION_NOTEFILE);
    
    // Add an ID to the request, which will be echo'ed
    // back in the response by the notecard itself.  This
    // helps us to identify the asynchronous response
    // without needing to have an additional state.
    JAddNumberToObject(req, "id", REQUESTID_DIAGNOTE);
    
    // Create the body
    J *body = JAddObjectToObject(req, "body");
    if (body == NULL) {
        JDelete(req);
        return;
    }
    
    // Fill-in the body
    struct mallinfo mem_info = mallinfo();
    
    JAddNumberToObject(body, "mem.alloc.bytes", (JNUMBER)mem_info.uordblks);
    JAddNumberToObject(body, "mem.free.bytes", (JNUMBER)mem_info.fordblks);
    JAddNumberToObject(body, "mem.heap.bytes", (JNUMBER)MX_Heap_Size(NULL));
    JAddNumberToObject(body, "voltage", (JNUMBER)MX_ADC_A0_Voltage());

    These lines are constructing a JSON object that looks like the following:

    Code Block
    languagejson
    {
      "req": "note.add",
      "sync": true,
      "file": "*#diag.qo",
      "id": 9171979,
      "body": {
        "mem.alloc.bytes": 1004,
        "mem.free.bytes": 888,
        "mem.heap.bytes": 53488,
        "voltage": 3.261
      }
    }

    NOTE: The asterisks (*) in the file name will be expanded to include the Sparrow's UID. For the Note above, this operation tips the Note size greater than MESSAGE_MAX_BODY which causes the body to be chunked and delivered over the LoRa network in two parts.

    Code Block
    languagec
    // Send request to the gateway
    noteSendToGatewayAsync(req, true);

    This line submits the Note to the Notecard attached to the Sparrow Gateway. The req parameter is the JSON object outlined above, and the true parameter informs the schedule that the application expects a response from the Notecard.

    Code Block
    languagec
    APP_PRINTF("diag: note request sent\r\n");

    This line logs the completion of the function.

  • static const char * diagStateName (int state);

    Code Block
    languagec
    switch (state) {
    case STATE_DIAG_ABORT:
        return "STATE_DIAG_ABORT";
    case STATE_DIAG_CHECK:
        return "STATE_DIAG_CHECK";
    case STATE_DIAG_ISR_XFER:
        return "STATE_DIAG_ISR_XFER";
    default:
    {
        static char undefined_state[20];
        schedStateName(state, undefined_state, sizeof(undefined_state));
        return undefined_state;
    }
    }

    The function translates the int value of an application specific state into a const char * description of the state. In the default case, where a state is unrecognized, it will defer to the analogous system API, which will either transcribe the state or simply print the integer's string representation.

  • static bool registerNotefileTemplate();

    Code Block
    languagec
    APP_PRINTF("diag: template registration request\r\n");

    This line simply logs the intent of the function.

    Code Block
    languagec
    // Create the request
    J *req = NoteNewRequest("note.template");
    if (req == NULL) {
        return false;
    }
    
    // Fill-in request parameters.  Note that in order to minimize
    // the size of the over-the-air JSON we're using a special format
    // for the "file" parameter implemented by the gateway, in which
    // a "file" parameter beginning with * will have that character
    // substituted with the textified Sparrow node address.
    JAddStringToObject(req, "file", APPLICATION_NOTEFILE);
    
    // Add an ID to the request, which will be echo'ed
    // back in the response by the notecard itself.  This
    // helps us to identify the asynchronous response
    // without needing to have an additional state.
    JAddNumberToObject(req, "id", REQUESTID_TEMPLATE);
    
    // Create the body
    J *body = JAddObjectToObject(req, "body");
    if (body == NULL) {
        JDelete(req);
        return false;
    }
    
    // Fill-in the body template
    JAddNumberToObject(body, "mem.alloc.bytes", TINT32);
    JAddNumberToObject(body, "mem.free.bytes", TINT32);
    JAddNumberToObject(body, "mem.heap.bytes", TINT32);
    JAddNumberToObject(body, "voltage", TFLOAT32);

    These lines are constructing a JSON object that looks like the following:

    Code Block
    languagejson
    {
      "req": "note.template",
      "file": "*#diag.qo",
      "id": 19790917,
      "body": {
        "mem.alloc.bytes": 14,
        "mem.free.bytes": 14,
        "mem.heap.bytes": 14,
        "voltage": 14.1
      }
    }

    Code Block
    // Send request to the gateway
    noteSendToGatewayAsync(req, true);

    This line submits the Note to the Notecard attached to the Sparrow Gateway. The req parameter is the JSON object outlined above, and the true parameter informs the schedule that the application expects a response from the Notecard.

    Code Block
    return true;

    This line informs the caller we were able to successfully create and submit the Note to the Notecard attached to the Sparrow Gateway.

  • static inline void resetIsrValues(applicationContext *ctx);

    Code Block
    languagec
    APP_PRINTF("diag: resetting ISR values\r\n");

    This line logs the entry into the function.

    Code Block
    languagec
    ctx->isrCount = 0;
    ctx->isrOverflow = false;
    for (size_t i = 0 ; i < ISR_MAX_CALL_RETENTION ; ++i) {
        ctx->isrParams[i].appID = 0;
        ctx->isrParams[i].pins = 0;
    }

    These lines will reset all metadata and parameters related to the ISR.

...